Compare commits
8 Commits
net8-basel
...
8.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 439de1500f | |||
| f2f10b819b | |||
| 44411ec165 | |||
| 66093db5fa | |||
| 04d3805fae | |||
| 44d91611a9 | |||
| 7ee5dfa119 | |||
| 74d07e77e2 |
12
CLAUDE.md
12
CLAUDE.md
@@ -11,14 +11,12 @@ The AyaNova v8 web client, in `ayanova/`. Vue 2.6, Vuetify 2.6, Vuex, Vue Router
|
|||||||
JavaScript (no TypeScript), Vue CLI 4 / webpack 4. Stack details:
|
JavaScript (no TypeScript), Vue CLI 4 / webpack 4. Stack details:
|
||||||
`../raven/devdocs/modernization/CODEBASE-REFERENCE.md`.
|
`../raven/devdocs/modernization/CODEBASE-REFERENCE.md`.
|
||||||
|
|
||||||
## During the .NET 10 port
|
## Release 8.3.0
|
||||||
|
|
||||||
The port is back-end only. **No changes in this repo** unless John asks, apart from a
|
The .NET 10 port is merged into `main` here too (2026-10-23); its only client changes were
|
||||||
rebuild if the port needs one. Whether the client gets rebuilt in this pass is an open
|
the version bump and the Sass `quietDeps` build setting. Release work continues on `main`.
|
||||||
question in `../raven/devdocs/modernization/NET10-PORT.md`.
|
**No changes in this repo** unless John asks. A "next release" case that touches the
|
||||||
|
client gets its own commits on `main`, under its own case number.
|
||||||
If a port item touches the client (for example removing a package from both repos), it
|
|
||||||
goes on a `net10-port` branch here too, with its own commit.
|
|
||||||
|
|
||||||
## Constraints
|
## Constraints
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"pluginsFile": "tests/e2e/plugins/index.js",
|
|
||||||
"baseUrl": "http://localhost:7575",
|
|
||||||
"defaultCommandTimeout":10000,
|
|
||||||
"env": {
|
|
||||||
"adminusername": "superuser",
|
|
||||||
"adminpassword": "l3tm3in"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
32
ayanova/package-lock.json
generated
32
ayanova/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ayanova",
|
"name": "ayanova",
|
||||||
"version": "8.2.4",
|
"version": "8.3.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ayanova",
|
"name": "ayanova",
|
||||||
"version": "8.2.4",
|
"version": "8.3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.3.0",
|
"@fortawesome/fontawesome-svg-core": "^1.3.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"chartjs-adapter-luxon": "^0.2.2",
|
"chartjs-adapter-luxon": "^0.2.2",
|
||||||
"core-js": "^3.23.1",
|
"core-js": "^3.23.1",
|
||||||
"dompurify": "^2.3.8",
|
"dompurify": "^2.5.9",
|
||||||
"fontsource-roboto": "^3.1.5",
|
"fontsource-roboto": "^3.1.5",
|
||||||
"github-markdown-css": "^4.0.0",
|
"github-markdown-css": "^4.0.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"vue-currency-input": "1.22.3",
|
"vue-currency-input": "1.22.3",
|
||||||
"vue-router": "^3.5.4",
|
"vue-router": "^3.5.4",
|
||||||
"vue-signature": "^2.5.5",
|
"vue-signature": "^2.5.5",
|
||||||
"vuetify": "^2.6.6",
|
"vuetify": "^2.7.2",
|
||||||
"vuex": "^3.6.2",
|
"vuex": "^3.6.2",
|
||||||
"vuex-persistedstate": "^2.7.1"
|
"vuex-persistedstate": "^2.7.1"
|
||||||
},
|
},
|
||||||
@@ -6190,9 +6190,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "2.3.8",
|
"version": "2.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.9.tgz",
|
||||||
"integrity": "sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw=="
|
"integrity": "sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA=="
|
||||||
},
|
},
|
||||||
"node_modules/domutils": {
|
"node_modules/domutils": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
@@ -15398,9 +15398,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/vuetify": {
|
"node_modules/vuetify": {
|
||||||
"version": "2.6.6",
|
"version": "2.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.7.2.tgz",
|
||||||
"integrity": "sha512-H4KtxDFmDN8QiTRiGfBySyjMhVaHAJTKB0llGGKZT5jKxtnx9gvEtMWXKtVuRP0NJJP0H6xBPJHNOH7nT18qiQ==",
|
"integrity": "sha512-qr04ww7uzAPQbpk751x4fSdjsJ+zREzjQ/rBlcQGuWS6MIMFMXcXcwvp4+/tnGsULZxPMWfQ0kmZmg5Yc/XzgQ==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/johnleider"
|
"url": "https://github.com/sponsors/johnleider"
|
||||||
@@ -21772,9 +21772,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dompurify": {
|
"dompurify": {
|
||||||
"version": "2.3.8",
|
"version": "2.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.9.tgz",
|
||||||
"integrity": "sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw=="
|
"integrity": "sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA=="
|
||||||
},
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
@@ -29104,9 +29104,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"vuetify": {
|
"vuetify": {
|
||||||
"version": "2.6.6",
|
"version": "2.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.7.2.tgz",
|
||||||
"integrity": "sha512-H4KtxDFmDN8QiTRiGfBySyjMhVaHAJTKB0llGGKZT5jKxtnx9gvEtMWXKtVuRP0NJJP0H6xBPJHNOH7nT18qiQ==",
|
"integrity": "sha512-qr04ww7uzAPQbpk751x4fSdjsJ+zREzjQ/rBlcQGuWS6MIMFMXcXcwvp4+/tnGsULZxPMWfQ0kmZmg5Yc/XzgQ==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"vuetify-loader": {
|
"vuetify-loader": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ayanova",
|
"name": "ayanova",
|
||||||
"version": "8.2.4",
|
"version": "8.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"chartjs-adapter-luxon": "^0.2.2",
|
"chartjs-adapter-luxon": "^0.2.2",
|
||||||
"core-js": "^3.23.1",
|
"core-js": "^3.23.1",
|
||||||
"dompurify": "^2.3.8",
|
"dompurify": "^2.5.9",
|
||||||
"fontsource-roboto": "^3.1.5",
|
"fontsource-roboto": "^3.1.5",
|
||||||
"github-markdown-css": "^4.0.0",
|
"github-markdown-css": "^4.0.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"vue-currency-input": "1.22.3",
|
"vue-currency-input": "1.22.3",
|
||||||
"vue-router": "^3.5.4",
|
"vue-router": "^3.5.4",
|
||||||
"vue-signature": "^2.5.5",
|
"vue-signature": "^2.5.5",
|
||||||
"vuetify": "^2.6.6",
|
"vuetify": "^2.7.2",
|
||||||
"vuex": "^3.6.2",
|
"vuex": "^3.6.2",
|
||||||
"vuex-persistedstate": "^2.7.1"
|
"vuex-persistedstate": "^2.7.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default {
|
export default {
|
||||||
version: "8.2.4",
|
version: "8.3.0",
|
||||||
copyright: "© 1999-2025, Ground Zero Tech-Works Inc."
|
copyright: "© 1999-2026, Ground Zero Tech-Works Inc."
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -646,6 +646,20 @@ export default {
|
|||||||
return str != null && typeof str.valueOf() === "string";
|
return str != null && typeof str.valueOf() === "string";
|
||||||
},
|
},
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
|
// case 4721: escape text that goes into HTML (v-html) so it shows as text
|
||||||
|
//
|
||||||
|
escapeHtml: function(str) {
|
||||||
|
if (str === null || str === undefined) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return String(str)
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
},
|
||||||
|
///////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -115,9 +115,13 @@
|
|||||||
v-model="notes"
|
v-model="notes"
|
||||||
:label="$ay.t('AttachmentNotes')"
|
:label="$ay.t('AttachmentNotes')"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
<v-btn color="primary" text @click="upload">{{
|
<v-btn
|
||||||
$ay.t("Upload")
|
v-if="uploadFiles && uploadFiles.length > 0"
|
||||||
}}</v-btn>
|
color="primary"
|
||||||
|
text
|
||||||
|
@click="upload"
|
||||||
|
>{{ $ay.t("Upload") }}</v-btn
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
</v-tabs-items>
|
</v-tabs-items>
|
||||||
|
|||||||
@@ -26,11 +26,9 @@
|
|||||||
>
|
>
|
||||||
<template v-slot:event="{ event, eventSummary }">
|
<template v-slot:event="{ event, eventSummary }">
|
||||||
<div>
|
<div>
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<span :class="event.textColor + '--text'"
|
||||||
<span
|
><gz-vnodes :vnodes="eventSummary()"/></span
|
||||||
:class="event.textColor + '--text'"
|
><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
||||||
v-html="eventSummary()"
|
|
||||||
/><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
|
||||||
$ayiLock</v-icon
|
$ayiLock</v-icon
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,11 +25,9 @@
|
|||||||
>
|
>
|
||||||
<template v-slot:event="{ event, eventSummary }">
|
<template v-slot:event="{ event, eventSummary }">
|
||||||
<div>
|
<div>
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<span :class="event.textColor + '--text'"
|
||||||
<span
|
><gz-vnodes :vnodes="eventSummary()"/></span
|
||||||
:class="event.textColor + '--text'"
|
><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
||||||
v-html="eventSummary()"
|
|
||||||
/><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
|
||||||
$ayiLock</v-icon
|
$ayiLock</v-icon
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,11 +26,9 @@
|
|||||||
>
|
>
|
||||||
<template v-slot:event="{ event, eventSummary }">
|
<template v-slot:event="{ event, eventSummary }">
|
||||||
<div>
|
<div>
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<span :class="event.textColor + '--text'"
|
||||||
<span
|
><gz-vnodes :vnodes="eventSummary()"/></span
|
||||||
:class="event.textColor + '--text'"
|
><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
||||||
v-html="eventSummary()"
|
|
||||||
/><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
|
||||||
$ayiLock</v-icon
|
$ayiLock</v-icon
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,10 +31,7 @@
|
|||||||
<span v-if="options.title" class="ml-5"> {{ options.title }} </span>
|
<span v-if="options.title" class="ml-5"> {{ options.title }} </span>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<v-card-text
|
<v-card-text class="text-body-1 text-sm-h6 my-5" v-html="safeMessage">
|
||||||
class="text-body-1 text-sm-h6 my-5"
|
|
||||||
v-html="options.message"
|
|
||||||
>
|
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
@@ -82,6 +79,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import DOMPurify from "dompurify";
|
||||||
export default {
|
export default {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
width: 290,
|
width: 290,
|
||||||
@@ -97,6 +95,12 @@ export default {
|
|||||||
type: "info" //one of success, info, question, warning, and error, see v-alert docs for more info
|
type: "info" //one of success, info, question, warning, and error, see v-alert docs for more info
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
computed: {
|
||||||
|
//case 4721: messages can hold formatting (line breaks), but never script: sanitised before it is shown
|
||||||
|
safeMessage() {
|
||||||
|
return DOMPurify.sanitize(this.options.message || "");
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open(options) {
|
open(options) {
|
||||||
if (options.message.includes("\n")) {
|
if (options.message.includes("\n")) {
|
||||||
|
|||||||
@@ -592,7 +592,9 @@ export default {
|
|||||||
|
|
||||||
let PurchasedFrom = "-";
|
let PurchasedFrom = "-";
|
||||||
if (r.purchaseFromVendorId) {
|
if (r.purchaseFromVendorId) {
|
||||||
PurchasedFrom = `<a href="/vendors/${r.purchaseFromVendorId}"> ${r.purchasedFromVendor}</a>`;
|
PurchasedFrom = `<a href="/vendors/${
|
||||||
|
r.purchaseFromVendorId
|
||||||
|
}"> ${window.$gz.util.escapeHtml(r.purchasedFromVendor)}</a>`; //case 4721: escaped
|
||||||
}
|
}
|
||||||
let PurchasedDate = "-";
|
let PurchasedDate = "-";
|
||||||
if (r.purchaseDate) {
|
if (r.purchaseDate) {
|
||||||
@@ -609,7 +611,7 @@ export default {
|
|||||||
"UnitPurchasedDate"
|
"UnitPurchasedDate"
|
||||||
)}: ${PurchasedDate}<br/>${this.$ay.t(
|
)}: ${PurchasedDate}<br/>${this.$ay.t(
|
||||||
"UnitReceipt"
|
"UnitReceipt"
|
||||||
)}: ${r.purchaseReceiptNumber ?? "-"}`;
|
)}: ${window.$gz.util.escapeHtml(r.purchaseReceiptNumber ?? "-")}`; //case 4721: escaped
|
||||||
|
|
||||||
let RecentWorkOrderList = "";
|
let RecentWorkOrderList = "";
|
||||||
if (r.recentWorkOrders.length > 0) {
|
if (r.recentWorkOrders.length > 0) {
|
||||||
|
|||||||
@@ -584,7 +584,9 @@ export default {
|
|||||||
|
|
||||||
let PurchasedFrom = "-";
|
let PurchasedFrom = "-";
|
||||||
if (r.purchaseFromVendorId) {
|
if (r.purchaseFromVendorId) {
|
||||||
PurchasedFrom = `<a href="/vendors/${r.purchaseFromVendorId}"> ${r.purchasedFromVendor}</a>`;
|
PurchasedFrom = `<a href="/vendors/${
|
||||||
|
r.purchaseFromVendorId
|
||||||
|
}"> ${window.$gz.util.escapeHtml(r.purchasedFromVendor)}</a>`; //case 4721: escaped
|
||||||
}
|
}
|
||||||
let PurchasedDate = "-";
|
let PurchasedDate = "-";
|
||||||
if (r.purchaseDate) {
|
if (r.purchaseDate) {
|
||||||
@@ -601,7 +603,7 @@ export default {
|
|||||||
"UnitPurchasedDate"
|
"UnitPurchasedDate"
|
||||||
)}: ${PurchasedDate}<br/>${this.$ay.t(
|
)}: ${PurchasedDate}<br/>${this.$ay.t(
|
||||||
"UnitReceipt"
|
"UnitReceipt"
|
||||||
)}: ${r.purchaseReceiptNumber ?? "-"}`;
|
)}: ${window.$gz.util.escapeHtml(r.purchaseReceiptNumber ?? "-")}`; //case 4721: escaped
|
||||||
|
|
||||||
let RecentWorkOrderList = "";
|
let RecentWorkOrderList = "";
|
||||||
if (r.recentWorkOrders.length > 0) {
|
if (r.recentWorkOrders.length > 0) {
|
||||||
|
|||||||
11
ayanova/src/components/vnodes-control.vue
Normal file
11
ayanova/src/components/vnodes-control.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script>
|
||||||
|
//case 4722: renders the element(s) passed in vnodes
|
||||||
|
//(Vuetify 2.7 calendar eventSummary() returns an element, not an HTML string)
|
||||||
|
export default {
|
||||||
|
functional: true,
|
||||||
|
props: {
|
||||||
|
vnodes: { type: [Object, Array], default: null }
|
||||||
|
},
|
||||||
|
render: (h, ctx) => ctx.props.vnodes
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -368,9 +368,9 @@
|
|||||||
</v-list-item-avatar>
|
</v-list-item-avatar>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-list-item-title>{{ item.name }}</v-list-item-title>
|
<v-list-item-title>{{ item.name }}</v-list-item-title>
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- case 4721: plain text, the address is never HTML -->
|
||||||
<v-list-item-subtitle
|
<v-list-item-subtitle
|
||||||
v-html="item.viz"
|
v-text="item.viz"
|
||||||
></v-list-item-subtitle>
|
></v-list-item-subtitle>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
|||||||
@@ -624,7 +624,9 @@ export default {
|
|||||||
|
|
||||||
let PurchasedFrom = "-";
|
let PurchasedFrom = "-";
|
||||||
if (r.purchaseFromVendorId) {
|
if (r.purchaseFromVendorId) {
|
||||||
PurchasedFrom = `<a href="/vendors/${r.purchaseFromVendorId}"> ${r.purchasedFromVendor}</a>`;
|
PurchasedFrom = `<a href="/vendors/${
|
||||||
|
r.purchaseFromVendorId
|
||||||
|
}"> ${window.$gz.util.escapeHtml(r.purchasedFromVendor)}</a>`; //case 4721: escaped
|
||||||
}
|
}
|
||||||
let PurchasedDate = "-";
|
let PurchasedDate = "-";
|
||||||
if (r.purchaseDate) {
|
if (r.purchaseDate) {
|
||||||
@@ -641,7 +643,7 @@ export default {
|
|||||||
"UnitPurchasedDate"
|
"UnitPurchasedDate"
|
||||||
)}: ${PurchasedDate}<br/>${this.$ay.t(
|
)}: ${PurchasedDate}<br/>${this.$ay.t(
|
||||||
"UnitReceipt"
|
"UnitReceipt"
|
||||||
)}: ${r.purchaseReceiptNumber ?? "-"}`;
|
)}: ${window.$gz.util.escapeHtml(r.purchaseReceiptNumber ?? "-")}`; //case 4721: escaped
|
||||||
|
|
||||||
let RecentWorkOrderList = "";
|
let RecentWorkOrderList = "";
|
||||||
if (r.recentWorkOrders.length > 0) {
|
if (r.recentWorkOrders.length > 0) {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import attachmentControl from "./components/attachment-control.vue";
|
|||||||
import chartLineControl from "./components/chart-line-control.vue";
|
import chartLineControl from "./components/chart-line-control.vue";
|
||||||
import chartBarControl from "./components/chart-bar-control.vue";
|
import chartBarControl from "./components/chart-bar-control.vue";
|
||||||
import chartBarHorizontalControl from "./components/chart-bar-horizontal-control.vue";
|
import chartBarHorizontalControl from "./components/chart-bar-horizontal-control.vue";
|
||||||
|
import vnodesControl from "./components/vnodes-control.vue";
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// LIBS AND GLOBAL ITEMS
|
// LIBS AND GLOBAL ITEMS
|
||||||
@@ -180,6 +181,7 @@ Vue.component("gz-attachments", attachmentControl);
|
|||||||
Vue.component("gz-chart-line", chartLineControl);
|
Vue.component("gz-chart-line", chartLineControl);
|
||||||
Vue.component("gz-chart-bar", chartBarControl);
|
Vue.component("gz-chart-bar", chartBarControl);
|
||||||
Vue.component("gz-chart-bar-horizontal", chartBarHorizontalControl);
|
Vue.component("gz-chart-bar-horizontal", chartBarHorizontalControl);
|
||||||
|
Vue.component("gz-vnodes", vnodesControl);
|
||||||
//3rd party components
|
//3rd party components
|
||||||
Vue.use(VueCurrencyInput);
|
Vue.use(VueCurrencyInput);
|
||||||
/////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -16,8 +16,11 @@
|
|||||||
class="grey lighten-2"
|
class="grey lighten-2"
|
||||||
:src="smallUrl"
|
:src="smallUrl"
|
||||||
:alt="$ay.t('SmallLogo')"
|
:alt="$ay.t('SmallLogo')"
|
||||||
|
@load="hasSmall = true"
|
||||||
|
@error="hasSmall = false"
|
||||||
/>
|
/>
|
||||||
<v-file-input
|
<v-file-input
|
||||||
|
v-if="rights.change"
|
||||||
v-model="uploadSmall"
|
v-model="uploadSmall"
|
||||||
dense
|
dense
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
@@ -26,20 +29,31 @@
|
|||||||
:rules="rules"
|
:rules="rules"
|
||||||
data-cy="uploadSmall"
|
data-cy="uploadSmall"
|
||||||
></v-file-input>
|
></v-file-input>
|
||||||
<v-btn color="primary" text @click="remove('small')">{{
|
<v-btn
|
||||||
$ay.t("Delete")
|
v-if="rights.change && hasSmall"
|
||||||
}}</v-btn>
|
color="primary"
|
||||||
<v-btn color="primary" text @click="upload('small')">{{
|
text
|
||||||
$ay.t("Upload")
|
@click="remove('small')"
|
||||||
}}</v-btn>
|
>{{ $ay.t("Delete") }}</v-btn
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
v-if="rights.change && uploadSmall"
|
||||||
|
color="primary"
|
||||||
|
text
|
||||||
|
@click="upload('small')"
|
||||||
|
>{{ $ay.t("Upload") }}</v-btn
|
||||||
|
>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" class="mt-10">
|
<v-col cols="12" class="mt-10">
|
||||||
<img
|
<img
|
||||||
class="mt-10 grey lighten-2"
|
class="mt-10 grey lighten-2"
|
||||||
:src="mediumUrl"
|
:src="mediumUrl"
|
||||||
:alt="$ay.t('MediumLogo')"
|
:alt="$ay.t('MediumLogo')"
|
||||||
|
@load="hasMedium = true"
|
||||||
|
@error="hasMedium = false"
|
||||||
/>
|
/>
|
||||||
<v-file-input
|
<v-file-input
|
||||||
|
v-if="rights.change"
|
||||||
v-model="uploadMedium"
|
v-model="uploadMedium"
|
||||||
dense
|
dense
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
@@ -47,20 +61,31 @@
|
|||||||
:label="$ay.t('MediumLogo')"
|
:label="$ay.t('MediumLogo')"
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
></v-file-input>
|
></v-file-input>
|
||||||
<v-btn color="primary" text @click="remove('medium')">{{
|
<v-btn
|
||||||
$ay.t("Delete")
|
v-if="rights.change && hasMedium"
|
||||||
}}</v-btn>
|
color="primary"
|
||||||
<v-btn color="primary" text @click="upload('medium')">{{
|
text
|
||||||
$ay.t("Upload")
|
@click="remove('medium')"
|
||||||
}}</v-btn>
|
>{{ $ay.t("Delete") }}</v-btn
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
v-if="rights.change && uploadMedium"
|
||||||
|
color="primary"
|
||||||
|
text
|
||||||
|
@click="upload('medium')"
|
||||||
|
>{{ $ay.t("Upload") }}</v-btn
|
||||||
|
>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" class="mt-10">
|
<v-col cols="12" class="mt-10">
|
||||||
<img
|
<img
|
||||||
class="mt-10 grey lighten-2"
|
class="mt-10 grey lighten-2"
|
||||||
:src="largeUrl"
|
:src="largeUrl"
|
||||||
:alt="$ay.t('LargeLogo')"
|
:alt="$ay.t('LargeLogo')"
|
||||||
|
@load="hasLarge = true"
|
||||||
|
@error="hasLarge = false"
|
||||||
/>
|
/>
|
||||||
<v-file-input
|
<v-file-input
|
||||||
|
v-if="rights.change"
|
||||||
v-model="uploadLarge"
|
v-model="uploadLarge"
|
||||||
dense
|
dense
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
@@ -68,12 +93,20 @@
|
|||||||
:label="$ay.t('LargeLogo')"
|
:label="$ay.t('LargeLogo')"
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
></v-file-input>
|
></v-file-input>
|
||||||
<v-btn color="primary" text @click="remove('large')">{{
|
<v-btn
|
||||||
$ay.t("Delete")
|
v-if="rights.change && hasLarge"
|
||||||
}}</v-btn>
|
color="primary"
|
||||||
<v-btn color="primary" text @click="upload('large')">{{
|
text
|
||||||
$ay.t("Upload")
|
@click="remove('large')"
|
||||||
}}</v-btn>
|
>{{ $ay.t("Delete") }}</v-btn
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
v-if="rights.change && uploadLarge"
|
||||||
|
color="primary"
|
||||||
|
text
|
||||||
|
@click="upload('large')"
|
||||||
|
>{{ $ay.t("Upload") }}</v-btn
|
||||||
|
>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-form>
|
</v-form>
|
||||||
@@ -94,6 +127,10 @@ export default {
|
|||||||
mediumUrl: null,
|
mediumUrl: null,
|
||||||
largeUrl: null,
|
largeUrl: null,
|
||||||
smallUrl: null,
|
smallUrl: null,
|
||||||
|
//case 4602: Delete is shown only for a logo that exists (set by the image's load / error)
|
||||||
|
hasSmall: false,
|
||||||
|
hasMedium: false,
|
||||||
|
hasLarge: false,
|
||||||
fileSizeExceededWarning: "",
|
fileSizeExceededWarning: "",
|
||||||
formState: {
|
formState: {
|
||||||
ready: false,
|
ready: false,
|
||||||
@@ -119,7 +156,6 @@ export default {
|
|||||||
try {
|
try {
|
||||||
await initForm(vm);
|
await initForm(vm);
|
||||||
vm.formState.ready = true;
|
vm.formState.ready = true;
|
||||||
vm.readOnly = !vm.rights.change;
|
|
||||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||||
//NOTE: this would normally be in getDataFromAPI but this form doesn't really need that function so doing it here
|
//NOTE: this would normally be in getDataFromAPI but this form doesn't really need that function so doing it here
|
||||||
generateMenu(vm);
|
generateMenu(vm);
|
||||||
@@ -171,12 +207,15 @@ export default {
|
|||||||
switch (size) {
|
switch (size) {
|
||||||
case "small":
|
case "small":
|
||||||
vm.smallUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
vm.smallUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
||||||
|
vm.uploadSmall = null;
|
||||||
break;
|
break;
|
||||||
case "medium":
|
case "medium":
|
||||||
vm.mediumUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
vm.mediumUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
||||||
|
vm.uploadMedium = null;
|
||||||
break;
|
break;
|
||||||
case "large":
|
case "large":
|
||||||
vm.largeUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
vm.largeUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
||||||
|
vm.uploadLarge = null;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
@@ -207,12 +246,15 @@ export default {
|
|||||||
switch (size) {
|
switch (size) {
|
||||||
case "small":
|
case "small":
|
||||||
vm.smallUrl = null;
|
vm.smallUrl = null;
|
||||||
|
vm.hasSmall = false;
|
||||||
break;
|
break;
|
||||||
case "medium":
|
case "medium":
|
||||||
vm.mediumUrl = null;
|
vm.mediumUrl = null;
|
||||||
|
vm.hasMedium = false;
|
||||||
break;
|
break;
|
||||||
case "large":
|
case "large":
|
||||||
vm.largeUrl = null;
|
vm.largeUrl = null;
|
||||||
|
vm.hasLarge = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -103,11 +103,9 @@
|
|||||||
<v-icon small :color="event.textColor" class="mt-n3 mr-1">{{
|
<v-icon small :color="event.textColor" class="mt-n3 mr-1">{{
|
||||||
iconForEvent(event.type)
|
iconForEvent(event.type)
|
||||||
}}</v-icon>
|
}}</v-icon>
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<span :class="event.textColor + '--text'"
|
||||||
<span
|
><gz-vnodes :vnodes="eventSummary()"/></span
|
||||||
:class="event.textColor + '--text'"
|
><v-icon
|
||||||
v-html="eventSummary()"
|
|
||||||
/><v-icon
|
|
||||||
v-if="!event.editable"
|
v-if="!event.editable"
|
||||||
x-small
|
x-small
|
||||||
:color="event.textColor"
|
:color="event.textColor"
|
||||||
|
|||||||
@@ -206,16 +206,9 @@ export default {
|
|||||||
vm.formState.serverError = res.error;
|
vm.formState.serverError = res.error;
|
||||||
window.$gz.form.setErrorBoxErrors(vm);
|
window.$gz.form.setErrorBoxErrors(vm);
|
||||||
} else {
|
} else {
|
||||||
let showInfo = res.data;
|
//case 4721: the excerpt is record text, so it is escaped; the search terms are highlighted around it
|
||||||
const searchTerms = vm.searchPhrase.replace(/[*]/gi, "").split(" ");
|
const searchTerms = vm.searchPhrase.replace(/[*]/gi, "").split(" ");
|
||||||
for (let i = 0; i < searchTerms.length; i++) {
|
item.info = highlightTerms(res.data || "", searchTerms);
|
||||||
const regEx = new RegExp(searchTerms[i], "ig");
|
|
||||||
const replaceMask =
|
|
||||||
"<span class='v-list-item__mask'>" + searchTerms[i] + "</span>";
|
|
||||||
showInfo = showInfo.replace(regEx, replaceMask);
|
|
||||||
}
|
|
||||||
|
|
||||||
item.info = showInfo;
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.$gz.errorHandler.handleFormError(error, vm);
|
window.$gz.errorHandler.handleFormError(error, vm);
|
||||||
@@ -357,4 +350,39 @@ async function populateSelectionLists(vm) {
|
|||||||
window.$gz.form.addNoSelectionItem(vm.selectLists.objectTypes);
|
window.$gz.form.addNoSelectionItem(vm.selectLists.objectTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// case 4721: excerpt as escaped text with the search terms highlighted (case insensitive);
|
||||||
|
// the terms are matched as plain text, not as a regular expression
|
||||||
|
function highlightTerms(text, terms) {
|
||||||
|
const esc = window.$gz.util.escapeHtml;
|
||||||
|
const lowerText = text.toLowerCase();
|
||||||
|
const lowerTerms = terms.map(z => z.toLowerCase()).filter(z => z.length > 0);
|
||||||
|
let html = "";
|
||||||
|
let pos = 0;
|
||||||
|
while (pos < text.length) {
|
||||||
|
let best = -1;
|
||||||
|
let bestLength = 0;
|
||||||
|
lowerTerms.forEach(t => {
|
||||||
|
const i = lowerText.indexOf(t, pos);
|
||||||
|
if (
|
||||||
|
i !== -1 &&
|
||||||
|
(best === -1 || i < best || (i === best && t.length > bestLength))
|
||||||
|
) {
|
||||||
|
best = i;
|
||||||
|
bestLength = t.length;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (best === -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
html +=
|
||||||
|
esc(text.substring(pos, best)) +
|
||||||
|
"<span class='v-list-item__mask'>" +
|
||||||
|
esc(text.substring(best, best + bestLength)) +
|
||||||
|
"</span>";
|
||||||
|
pos = best + bestLength;
|
||||||
|
}
|
||||||
|
return html + esc(text.substring(pos));
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -96,11 +96,9 @@
|
|||||||
<v-icon small :color="event.textColor" class="mr-1">{{
|
<v-icon small :color="event.textColor" class="mr-1">{{
|
||||||
iconForEvent(event.type)
|
iconForEvent(event.type)
|
||||||
}}</v-icon>
|
}}</v-icon>
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<span :class="event.textColor + '--text'"
|
||||||
<span
|
><gz-vnodes :vnodes="eventSummary()"/></span
|
||||||
:class="event.textColor + '--text'"
|
><v-icon
|
||||||
v-html="eventSummary()"
|
|
||||||
/><v-icon
|
|
||||||
v-if="!event.editable"
|
v-if="!event.editable"
|
||||||
x-small
|
x-small
|
||||||
:color="event.textColor"
|
:color="event.textColor"
|
||||||
|
|||||||
@@ -132,8 +132,9 @@
|
|||||||
|
|
||||||
<template v-slot:event="{ event, eventSummary }">
|
<template v-slot:event="{ event, eventSummary }">
|
||||||
<div class="v-event-draggable">
|
<div class="v-event-draggable">
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<span :class="event.textColor + '--text'"
|
||||||
<span :class="event.textColor + '--text'" v-html="eventSummary()" />
|
><gz-vnodes :vnodes="eventSummary()"
|
||||||
|
/></span>
|
||||||
<v-icon
|
<v-icon
|
||||||
v-if="!event.editable"
|
v-if="!event.editable"
|
||||||
x-small
|
x-small
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ module.exports = {
|
|||||||
|
|
||||||
transpileDependencies: ["vuetify"],
|
transpileDependencies: ["vuetify"],
|
||||||
|
|
||||||
|
//Silence Dart Sass deprecation warnings raised inside node_modules (Vuetify 2's own styles); our own styles still warn
|
||||||
|
css: {
|
||||||
|
loaderOptions: {
|
||||||
|
sass: { sassOptions: { quietDeps: true } },
|
||||||
|
scss: { sassOptions: { quietDeps: true } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
pwa: {
|
pwa: {
|
||||||
//https://cli.vuejs.org/core-plugins/pwa.html#configuration
|
//https://cli.vuejs.org/core-plugins/pwa.html#configuration
|
||||||
// manifestPath: "manifest.json",
|
// manifestPath: "manifest.json",
|
||||||
|
|||||||
Reference in New Issue
Block a user