Compare commits
18 Commits
f8ee7b2da7
...
8.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a5e551484 | |||
| a524f01dd6 | |||
| f6917dd089 | |||
| 7ae5ec486e | |||
| be3c45cd78 | |||
| 439de1500f | |||
| f2f10b819b | |||
| 44411ec165 | |||
| 66093db5fa | |||
| 04d3805fae | |||
| 44d91611a9 | |||
| 7ee5dfa119 | |||
| 74d07e77e2 | |||
| de03f56015 | |||
| 838ce9f390 | |||
| ab72022c08 | |||
| 8c3037bd86 | |||
| e4dcbd852e |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,4 +13,5 @@ Thumbs.db
|
||||
|
||||
# Environment
|
||||
.env.local
|
||||
.env.*.local
|
||||
.env.*.local
|
||||
/ayanova/logfile
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
# AyaNova v8 Build Environment Documentation
|
||||
|
||||
> **Purpose**: This document captures the exact build environment for AyaNova v8 as of February 2026. Use this to recreate the build environment on a new machine or in a container.
|
||||
|
||||
## Overview
|
||||
|
||||
AyaNova v8 consists of three main build components:
|
||||
|
||||
1. **Frontend** - Vue.js 2 application (JavaScript, not TypeScript)
|
||||
2. **Documentation** - MkDocs with Material theme
|
||||
3. **Backend** - ASP.NET Core 8.0 server (C#)
|
||||
|
||||
The frontend and docs are built first, then copied into the backend project's `wwwroot` folder before the backend is built and packaged for distribution.
|
||||
|
||||
---
|
||||
|
||||
## Current Build Machine Specifications
|
||||
|
||||
| Component | Version | Notes |
|
||||
|-----------|---------|-------|
|
||||
| Operating System | Windows 11 | ThinkPad X13 Gen 3 |
|
||||
| Node.js | v12.22.9 | **End-of-life** - do not upgrade without testing |
|
||||
| npm | (check with `npm -v`) | Comes with Node |
|
||||
| Python | 3.11.1 | For MkDocs |
|
||||
| .NET SDK | 8.0.x | For backend build |
|
||||
|
||||
---
|
||||
|
||||
## Frontend Build Environment
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
c:\data\code\
|
||||
├── raven-client\
|
||||
│ └── ayanova\ # Vue.js frontend project
|
||||
│ ├── src\ # Source code
|
||||
│ ├── public\ # Static assets
|
||||
│ ├── dist\ # Build output (generated)
|
||||
│ ├── package.json
|
||||
│ ├── vue.config.js
|
||||
│ └── buildrelease.bat
|
||||
├── raven\
|
||||
│ ├── server\
|
||||
│ │ └── AyaNova\
|
||||
│ │ └── wwwroot\ # Frontend files copied here after build
|
||||
│ └── docs\
|
||||
│ └── 8.0\
|
||||
│ └── ayanova\ # MkDocs source
|
||||
│ └── mkdocs.yml
|
||||
```
|
||||
|
||||
### Node.js Version: Critical Information
|
||||
|
||||
**Current version: Node.js 12.22.9**
|
||||
|
||||
This project uses an old Node version because:
|
||||
|
||||
1. The `fibers` package (used by sass-loader for faster Sass compilation) is a native C++ addon that stopped being maintained and doesn't compile on newer Node versions
|
||||
2. Various other dependencies have peer dependency requirements that break on Node 14+
|
||||
|
||||
**Do not upgrade Node without first testing in an isolated environment.**
|
||||
|
||||
### Key Dependencies
|
||||
|
||||
From `package.json`:
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| vue | 2.6.14 | Core framework (Vue 2, end-of-life Dec 2023) |
|
||||
| vuetify | 2.6.6 | UI component library |
|
||||
| @vue/cli-service | 4.5.15 | Build tooling |
|
||||
| webpack | 4.46.0 | Module bundler |
|
||||
| sass | 1.52.3 | Sass compiler |
|
||||
| fibers | 4.0.3 | **Problematic** - native addon for Sass |
|
||||
| monaco-editor | 0.30.1 | Code editor component |
|
||||
|
||||
### Build Process
|
||||
|
||||
The frontend build is executed via `buildrelease.bat`:
|
||||
|
||||
```batch
|
||||
@echo **************************************************************
|
||||
@echo ******************** BUILD CLIENT ****************************
|
||||
@echo **************************************************************
|
||||
rmdir c:\data\code\raven\server\AyaNova\wwwroot /s/q
|
||||
mkdir c:\data\code\raven\server\AyaNova\wwwroot
|
||||
cd c:\data\code\raven\docs\8.0\ayanova
|
||||
mkdocs build
|
||||
cd c:\data\code\raven-client\ayanova
|
||||
call npm run build
|
||||
xcopy c:\data\code\raven-client\ayanova\dist\* C:\data\code\raven\server\AyaNova\wwwroot\ /e
|
||||
pause
|
||||
```
|
||||
|
||||
This does:
|
||||
1. Clears and recreates the backend's wwwroot folder
|
||||
2. Builds the MkDocs documentation (output goes directly to wwwroot/docs via mkdocs.yml config)
|
||||
3. Runs `npm run build` which calls `vue-cli-service build`
|
||||
4. Copies the built frontend from `dist/` to the backend's `wwwroot/`
|
||||
|
||||
### Known Build Warnings (Safe to Ignore)
|
||||
|
||||
These warnings appear during every build and do not affect the output:
|
||||
|
||||
1. **Browserslist warning**: "caniuse-lite is outdated"
|
||||
- Harmless - just means browser compatibility database is old
|
||||
|
||||
2. **Dart Sass division warnings**: "Using / for division is deprecated"
|
||||
- Come from Vuetify's stylesheets, not your code
|
||||
- Will continue working until Dart Sass 2.0
|
||||
|
||||
3. **Asset size warnings**: Various files exceed 244 KiB
|
||||
- Expected with Monaco editor (ts.worker.js is 4.5MB)
|
||||
- Application still works correctly
|
||||
|
||||
### NPM Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"serve": "vue-cli-service serve", // Development server
|
||||
"build": "vue-cli-service build", // Production build
|
||||
"lint": "vue-cli-service lint" // Code linting
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation Build Environment
|
||||
|
||||
### MkDocs Configuration
|
||||
|
||||
From `mkdocs.yml`:
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Theme | Material |
|
||||
| Output directory | `../../../server/AyaNova/wwwroot/docs` |
|
||||
| Site URL | https://ayanova.com/docs/ |
|
||||
|
||||
### Required Python Packages
|
||||
|
||||
```bash
|
||||
pip install mkdocs mkdocs-material
|
||||
```
|
||||
|
||||
The `pymdownx` extensions (highlight, superfences) come bundled with mkdocs-material.
|
||||
|
||||
### Build Command
|
||||
|
||||
```bash
|
||||
cd c:\data\code\raven\docs\8.0\ayanova
|
||||
mkdocs build
|
||||
```
|
||||
|
||||
Output goes directly to the backend's wwwroot/docs folder.
|
||||
|
||||
---
|
||||
|
||||
## Backend Build Environment
|
||||
|
||||
### .NET Version
|
||||
|
||||
- **Target Framework**: .NET 8.0
|
||||
- **Project Type**: ASP.NET Core
|
||||
|
||||
### Build Tools Required
|
||||
|
||||
- .NET 8.0 SDK
|
||||
- Visual Studio 2022 or VS Code with C# extension
|
||||
|
||||
### Build Outputs
|
||||
|
||||
The backend build produces multiple deployment targets:
|
||||
1. Linux hosted (DigitalOcean service)
|
||||
2. Windows networked (IIS hosted)
|
||||
3. Windows single-user (self-contained)
|
||||
4. Linux server
|
||||
5. Linux desktop
|
||||
|
||||
Windows installers are created with Inno Setup.
|
||||
|
||||
---
|
||||
|
||||
## Recreating the Build Environment
|
||||
|
||||
### Option 1: Native Installation (Windows)
|
||||
|
||||
1. Install Node.js 12.22.9 specifically:
|
||||
- Download from https://nodejs.org/dist/v12.22.9/
|
||||
- Or use nvm-windows: `nvm install 12.22.9 && nvm use 12.22.9`
|
||||
|
||||
2. Install Python 3.11:
|
||||
- Download from python.org
|
||||
- Ensure it's in PATH
|
||||
|
||||
3. Install MkDocs:
|
||||
```bash
|
||||
pip install mkdocs mkdocs-material
|
||||
```
|
||||
|
||||
4. Install .NET 8.0 SDK:
|
||||
- Download from https://dotnet.microsoft.com/download
|
||||
|
||||
5. Clone/checkout repository from SVN
|
||||
|
||||
6. Install frontend dependencies:
|
||||
```bash
|
||||
cd c:\data\code\raven-client\ayanova
|
||||
npm ci
|
||||
```
|
||||
Note: Use `npm ci` (not `npm install`) to install exact versions from package-lock.json
|
||||
|
||||
### Option 2: Docker (Recommended for Reproducibility)
|
||||
|
||||
See `Dockerfile.frontend` and `Dockerfile.docs` in this repository for containerized builds that don't require any local installation except Docker.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "fibers" fails to install
|
||||
|
||||
This native module requires:
|
||||
- Python 2.7 or 3.x
|
||||
- Visual Studio Build Tools (Windows) or build-essential (Linux)
|
||||
- Specific Node.js version compatibility
|
||||
|
||||
If it fails, you can try removing it (it's optional):
|
||||
1. Remove `"fibers": "^4.0.3"` from package.json
|
||||
2. Remove the `fiber: require("fibers")` line from webpack.config.js
|
||||
3. Run `npm install` again
|
||||
|
||||
The build will be slightly slower but should work.
|
||||
|
||||
### Node version mismatch
|
||||
|
||||
If you see errors about incompatible Node versions:
|
||||
1. Check your Node version: `node -v`
|
||||
2. It must be 12.22.9 for guaranteed compatibility
|
||||
3. Use nvm to switch versions if needed
|
||||
|
||||
### "Module not found" errors
|
||||
|
||||
Try:
|
||||
```bash
|
||||
rm -rf node_modules
|
||||
rm package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Future Modernization Notes
|
||||
|
||||
When time permits, consider:
|
||||
|
||||
1. **Remove fibers dependency** - It's optional and causes most build issues
|
||||
2. **Upgrade to Vue 3 + Vite** - Much faster builds, better tooling, active support
|
||||
3. **Migrate to TypeScript** - Better IDE support and fewer runtime errors
|
||||
4. **Update Node to LTS** - Current LTS is Node 20.x
|
||||
|
||||
These are significant changes that should be done incrementally with thorough testing.
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-02-01 | Initial documentation created |
|
||||
59
CLAUDE.md
Normal file
59
CLAUDE.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# AyaNova v8 front end — instructions for Claude Code
|
||||
|
||||
Shared AyaNova context and working rules, from the server repo (the master):
|
||||
@../raven/CLAUDE.md
|
||||
|
||||
Everything below is specific to this repo.
|
||||
|
||||
## What this is
|
||||
|
||||
The AyaNova v8 web client, in `ayanova/`. Vue 2.6, Vuetify 2.6, Vuex, Vue Router, plain
|
||||
JavaScript (no TypeScript), Vue CLI 4 / webpack 4. Stack details:
|
||||
`../raven/devdocs/modernization/CODEBASE-REFERENCE.md`.
|
||||
|
||||
## Release 8.3.0
|
||||
|
||||
The .NET 10 port is merged into `main` here too (2026-10-23); its only client changes were
|
||||
the version bump and the Sass `quietDeps` build setting. Release work continues on `main`.
|
||||
**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.
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Node.js 12.22.9 exactly.** `fibers` breaks on anything newer. Check with `node -v` before building.
|
||||
- **No dependency changes**: no `npm install <package>`, no `npm update`, no version edits in `package.json`. Front-end upgrades are roadmap step F, not current work.
|
||||
- Install with `npm ci` (exact versions from `package-lock.json`), never `npm install`.
|
||||
- Vue 2 / Vuetify 2 are end of life. Do not upgrade them, and do not write Vue 3 style code.
|
||||
- Add `data-testid` attributes to elements that Cypress tests select.
|
||||
|
||||
## Commands
|
||||
|
||||
```powershell
|
||||
cd ayanova
|
||||
npm ci # first time, or after package-lock.json changes
|
||||
npm run serve # dev server with hot reload
|
||||
npm run build # production build into dist\
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## How the build reaches the server
|
||||
|
||||
`npm run build` writes to `ayanova\dist\`. `raven\build-release.bat` (or `buildrelease.bat`
|
||||
here) copies it into `raven\server\AyaNova\wwwroot\`. The .NET build does not produce
|
||||
`wwwroot`: whatever is there when the server is published is what ships.
|
||||
|
||||
Webpack content-hash file names (`app.<hash>.js` …) change on every build even when the
|
||||
contents do not. Compare file contents, not names.
|
||||
|
||||
Warnings on every build that are harmless: Browserslist "caniuse-lite is outdated"; Dart
|
||||
Sass division deprecation (from Vuetify); asset size (Monaco's workers).
|
||||
|
||||
## Code layout
|
||||
|
||||
`src/api/` API utilities, auth and error handling; `src/components/` about 80 components;
|
||||
`src/views/` about 100 pages; `src/plugins/` Vuetify setup; `router.js`, `store.js`,
|
||||
`main.js`, `App.vue`.
|
||||
|
||||
View prefixes: `acc-` accounting, `adm-` administration, `cust-` customers, `home-`
|
||||
dashboard, `inv-` inventory, `ops-` server operations, `svc-` service (work orders, quotes,
|
||||
PMs).
|
||||
@@ -1,47 +1,35 @@
|
||||
# AyaNova client ui
|
||||
# AyaNova client UI
|
||||
|
||||
## Project setup
|
||||
```
|
||||
First confirm all dependencies are installed as per BUILT-ENVIRONMENT.md doc. They are very specific and in many cases outdated.
|
||||
|
||||
Then clone/checkout repository from GIT
|
||||
1. Install the build tools exactly as listed in `C:\data\code\raven\devdocs\build\BUILD.md`.
|
||||
They are very specific, and in several cases outdated on purpose (Node.js 12.22.9).
|
||||
2. Clone this repository from Gitea into `C:\data\code\raven-client`.
|
||||
3. Install dependencies:
|
||||
|
||||
Finally:
|
||||
|
||||
Install frontend dependencies:
|
||||
|
||||
cd c:\data\code\raven-client\ayanova
|
||||
```
|
||||
cd C:\data\code\raven-client\ayanova
|
||||
npm ci
|
||||
|
||||
Note: Use `npm ci` (not `npm install`) to install exact versions from package-lock.json
|
||||
```
|
||||
```
|
||||
|
||||
Use `npm ci`, not `npm install`: it installs the exact versions in `package-lock.json`.
|
||||
|
||||
## Development server with hot reload
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
## Production build
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run your tests
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
Output goes to `dist\`. The release build copies it into the server's `wwwroot`.
|
||||
|
||||
## Lint
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Run your end-to-end tests
|
||||
```
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
### Run your unit tests
|
||||
```
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
@@ -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",
|
||||
"version": "8.2.4",
|
||||
"version": "8.3.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ayanova",
|
||||
"version": "8.2.4",
|
||||
"version": "8.3.1",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.3.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||
@@ -15,7 +15,7 @@
|
||||
"chart.js": "^2.9.4",
|
||||
"chartjs-adapter-luxon": "^0.2.2",
|
||||
"core-js": "^3.23.1",
|
||||
"dompurify": "^2.3.8",
|
||||
"dompurify": "^2.5.9",
|
||||
"fontsource-roboto": "^3.1.5",
|
||||
"github-markdown-css": "^4.0.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
@@ -31,7 +31,7 @@
|
||||
"vue-currency-input": "1.22.3",
|
||||
"vue-router": "^3.5.4",
|
||||
"vue-signature": "^2.5.5",
|
||||
"vuetify": "^2.6.6",
|
||||
"vuetify": "^2.7.2",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-persistedstate": "^2.7.1"
|
||||
},
|
||||
@@ -6190,9 +6190,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.8.tgz",
|
||||
"integrity": "sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw=="
|
||||
"version": "2.5.9",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.9.tgz",
|
||||
"integrity": "sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA=="
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "2.8.0",
|
||||
@@ -15398,9 +15398,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vuetify": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.6.tgz",
|
||||
"integrity": "sha512-H4KtxDFmDN8QiTRiGfBySyjMhVaHAJTKB0llGGKZT5jKxtnx9gvEtMWXKtVuRP0NJJP0H6xBPJHNOH7nT18qiQ==",
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.7.2.tgz",
|
||||
"integrity": "sha512-qr04ww7uzAPQbpk751x4fSdjsJ+zREzjQ/rBlcQGuWS6MIMFMXcXcwvp4+/tnGsULZxPMWfQ0kmZmg5Yc/XzgQ==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/johnleider"
|
||||
@@ -21772,9 +21772,9 @@
|
||||
}
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.8.tgz",
|
||||
"integrity": "sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw=="
|
||||
"version": "2.5.9",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.9.tgz",
|
||||
"integrity": "sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "2.8.0",
|
||||
@@ -29104,9 +29104,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"vuetify": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.6.tgz",
|
||||
"integrity": "sha512-H4KtxDFmDN8QiTRiGfBySyjMhVaHAJTKB0llGGKZT5jKxtnx9gvEtMWXKtVuRP0NJJP0H6xBPJHNOH7nT18qiQ==",
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.7.2.tgz",
|
||||
"integrity": "sha512-qr04ww7uzAPQbpk751x4fSdjsJ+zREzjQ/rBlcQGuWS6MIMFMXcXcwvp4+/tnGsULZxPMWfQ0kmZmg5Yc/XzgQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"vuetify-loader": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ayanova",
|
||||
"version": "8.2.4",
|
||||
"version": "8.3.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
@@ -16,7 +16,7 @@
|
||||
"chart.js": "^2.9.4",
|
||||
"chartjs-adapter-luxon": "^0.2.2",
|
||||
"core-js": "^3.23.1",
|
||||
"dompurify": "^2.3.8",
|
||||
"dompurify": "^2.5.9",
|
||||
"fontsource-roboto": "^3.1.5",
|
||||
"github-markdown-css": "^4.0.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
@@ -32,7 +32,7 @@
|
||||
"vue-currency-input": "1.22.3",
|
||||
"vue-router": "^3.5.4",
|
||||
"vue-signature": "^2.5.5",
|
||||
"vuetify": "^2.6.6",
|
||||
"vuetify": "^2.7.2",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-persistedstate": "^2.7.1"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
version: "8.2.4",
|
||||
copyright: "© 1999-2025, Ground Zero Tech-Works Inc."
|
||||
version: "8.3.1",
|
||||
//case 4630: the year is set when the client is built (vue.config.js)
|
||||
copyright: `© 1999-${process.env.VUE_APP_BUILD_YEAR}, Ground Zero Tech-Works Inc.`
|
||||
};
|
||||
|
||||
@@ -290,7 +290,7 @@ export default {
|
||||
if (
|
||||
window.$gz.dev &&
|
||||
window.location.hostname == "localhost" &&
|
||||
window.location.port == "8080"
|
||||
(window.location.port == "8080" || window.location.port == "8081")
|
||||
) {
|
||||
return "http://localhost:7575/api/v8.0/" + apiPath;
|
||||
}
|
||||
@@ -307,7 +307,7 @@ export default {
|
||||
if (
|
||||
window.$gz.dev &&
|
||||
window.location.hostname == "localhost" &&
|
||||
window.location.port == "8080"
|
||||
(window.location.port == "8080" || window.location.port == "8081")
|
||||
) {
|
||||
return "http://localhost:7575/docs/";
|
||||
}
|
||||
@@ -317,7 +317,7 @@ export default {
|
||||
if (
|
||||
window.$gz.dev &&
|
||||
window.location.hostname == "localhost" &&
|
||||
window.location.port == "8080"
|
||||
(window.location.port == "8080" || window.location.port == "8081")
|
||||
) {
|
||||
return "http://localhost:7575/cust/";
|
||||
}
|
||||
|
||||
@@ -646,6 +646,20 @@ export default {
|
||||
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, "'");
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
@@ -37,7 +37,7 @@ export default {
|
||||
// Get users default time zone
|
||||
// first check if overriden in useroptions
|
||||
// if not then use browsers own setting
|
||||
// if that is empty then final default of "America/New_York"
|
||||
// if the browser can't name its time zone then its current offset from UTC
|
||||
//https://www.iana.org/time-zones
|
||||
//https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
getResolvedTimeZoneName() {
|
||||
@@ -49,7 +49,14 @@ export default {
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(tz)) {
|
||||
return tz;
|
||||
} else {
|
||||
return "America/New_York";
|
||||
//case 4739: e.g. a new Windows time zone the browser doesn't know yet; this used to be "America/New_York"
|
||||
//a fixed offset zone in whole hours, the only kind every browser and luxon accept by name
|
||||
//(Etc/GMT signs are reversed: UTC-7 is Etc/GMT+7, which is what getTimezoneOffset returns, 420 minutes)
|
||||
const hours = Math.round(new Date().getTimezoneOffset() / 60);
|
||||
if (hours == 0) {
|
||||
return "Etc/GMT";
|
||||
}
|
||||
return `Etc/GMT${hours > 0 ? "+" : "-"}${Math.abs(hours)}`;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -115,9 +115,13 @@
|
||||
v-model="notes"
|
||||
:label="$ay.t('AttachmentNotes')"
|
||||
></v-text-field>
|
||||
<v-btn color="primary" text @click="upload">{{
|
||||
$ay.t("Upload")
|
||||
}}</v-btn>
|
||||
<v-btn
|
||||
v-if="uploadFiles && uploadFiles.length > 0"
|
||||
color="primary"
|
||||
text
|
||||
@click="upload"
|
||||
>{{ $ay.t("Upload") }}</v-btn
|
||||
>
|
||||
</div>
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
|
||||
@@ -26,11 +26,9 @@
|
||||
>
|
||||
<template v-slot:event="{ event, eventSummary }">
|
||||
<div>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<span
|
||||
:class="event.textColor + '--text'"
|
||||
v-html="eventSummary()"
|
||||
/><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
||||
<span :class="event.textColor + '--text'"
|
||||
><gz-vnodes :vnodes="eventSummary()"/></span
|
||||
><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
||||
$ayiLock</v-icon
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -25,11 +25,9 @@
|
||||
>
|
||||
<template v-slot:event="{ event, eventSummary }">
|
||||
<div>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<span
|
||||
:class="event.textColor + '--text'"
|
||||
v-html="eventSummary()"
|
||||
/><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
||||
<span :class="event.textColor + '--text'"
|
||||
><gz-vnodes :vnodes="eventSummary()"/></span
|
||||
><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
||||
$ayiLock</v-icon
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -26,11 +26,9 @@
|
||||
>
|
||||
<template v-slot:event="{ event, eventSummary }">
|
||||
<div>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<span
|
||||
:class="event.textColor + '--text'"
|
||||
v-html="eventSummary()"
|
||||
/><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
||||
<span :class="event.textColor + '--text'"
|
||||
><gz-vnodes :vnodes="eventSummary()"/></span
|
||||
><v-icon v-if="!event.editable" x-small :color="event.textColor">
|
||||
$ayiLock</v-icon
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -31,10 +31,7 @@
|
||||
<span v-if="options.title" class="ml-5"> {{ options.title }} </span>
|
||||
</v-card-title>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<v-card-text
|
||||
class="text-body-1 text-sm-h6 my-5"
|
||||
v-html="options.message"
|
||||
>
|
||||
<v-card-text class="text-body-1 text-sm-h6 my-5" v-html="safeMessage">
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
@@ -82,6 +79,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import DOMPurify from "dompurify";
|
||||
export default {
|
||||
data: () => ({
|
||||
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
|
||||
}
|
||||
}),
|
||||
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: {
|
||||
open(options) {
|
||||
if (options.message.includes("\n")) {
|
||||
|
||||
@@ -250,7 +250,7 @@ Ground Zero will comply with all applicable laws pertaining to personal data inc
|
||||
|
||||
A: Data obtained
|
||||
|
||||
Information about you (such as your email address, name, company information) is provided to us when you email us (such as when you email us directly, email us via our online form, email us via our online temporary Key request form) or when you register on our AyaNova Support Forum.
|
||||
Information about you (such as your email address, name, company information) is provided to us when you email us (such as when you email us directly, email us via our online form, email us via our online temporary Key request form).
|
||||
|
||||
Information you provided in your order (such as your company name, company contact information, contact name, along with the AyaNova products/services/subscriptions purchased) is sent to us from our payment processing partner with whom you placed your order.
|
||||
|
||||
@@ -260,7 +260,7 @@ Ground Zero will not share your information for marketing purposes with companie
|
||||
|
||||
C: Use and Sharing of Data
|
||||
|
||||
Personal data you provided when you contact us (such as when you email us directly, email us via our online form, email us via our online temporary Key request form or register on our AyaNova Support Forum) will be used to provide technical support, to respond to user inquiries, to provide temporary Key(s), to maintain case and bug issues reported by you, and to send you relevant AyaNova information.
|
||||
Personal data you provided when you contact us (such as when you email us directly, email us via our online form, or email us via our online temporary Key request form) will be used to provide technical support, to respond to user inquiries, to provide temporary Key(s), to maintain case and bug issues reported by you, and to send you relevant AyaNova information.
|
||||
|
||||
Ground Zero will also receive your company information you provided to our payment processing partner, so that we may process your product/service/subscription order.
|
||||
|
||||
@@ -282,8 +282,7 @@ E: Deleting of Data
|
||||
(i) Temporary key requests without subsequent purchase will be deleted within one year.
|
||||
(ii) Email correspondence without subsequent purchase will be deleted within two years except where required for maintaining case and support history.
|
||||
(iii) Email correspondence including temporary key requests with subsequent subscription purchase will be retained for the life of your subscription. Once a subscription has been allowed to lapse (not renewed), correspondence will be deleted within two years except where required for maintaining case and support history.
|
||||
(iv) Customer information obtained through AyaNova Support Forum registration will be deleted within three years of forum login inactivity.
|
||||
(v) Purchase information along with company information as provided by our payment processing partner will be retained for a minimum of six years after the end of the taxation year to which they relate as per the Canada Revenue Agency and no longer than 10 years after which will be deleted within one year.
|
||||
(iv) Purchase information along with company information as provided by our payment processing partner will be retained for a minimum of six years after the end of the taxation year to which they relate as per the Canada Revenue Agency and no longer than 10 years after which will be deleted within one year.
|
||||
|
||||
|
||||
I HAVE READ AND UNDERSTOOD ALL OF THE ABOVE TERMS AND CONDITIONS AND ACKNOWLEDGE THAT THESE TERMS AND CONDITIONS FORM PART OF THIS AGREEMENT. I AGREE THAT A PRINT-OUT OF THIS AGREEMENT WILL CONSTITUTE A "WRITING" UNDER ANY APPLICABLE LAWS.
|
||||
|
||||
@@ -592,7 +592,9 @@ export default {
|
||||
|
||||
let PurchasedFrom = "-";
|
||||
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 = "-";
|
||||
if (r.purchaseDate) {
|
||||
@@ -609,7 +611,7 @@ export default {
|
||||
"UnitPurchasedDate"
|
||||
)}: ${PurchasedDate}<br/>${this.$ay.t(
|
||||
"UnitReceipt"
|
||||
)}: ${r.purchaseReceiptNumber ?? "-"}`;
|
||||
)}: ${window.$gz.util.escapeHtml(r.purchaseReceiptNumber ?? "-")}`; //case 4721: escaped
|
||||
|
||||
let RecentWorkOrderList = "";
|
||||
if (r.recentWorkOrders.length > 0) {
|
||||
|
||||
@@ -584,7 +584,9 @@ export default {
|
||||
|
||||
let PurchasedFrom = "-";
|
||||
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 = "-";
|
||||
if (r.purchaseDate) {
|
||||
@@ -601,7 +603,7 @@ export default {
|
||||
"UnitPurchasedDate"
|
||||
)}: ${PurchasedDate}<br/>${this.$ay.t(
|
||||
"UnitReceipt"
|
||||
)}: ${r.purchaseReceiptNumber ?? "-"}`;
|
||||
)}: ${window.$gz.util.escapeHtml(r.purchaseReceiptNumber ?? "-")}`; //case 4721: escaped
|
||||
|
||||
let RecentWorkOrderList = "";
|
||||
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-content>
|
||||
<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-html="item.viz"
|
||||
v-text="item.viz"
|
||||
></v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
v-model="selectedContact"
|
||||
:items="contacts"
|
||||
:label="$ay.t('Contacts')"
|
||||
data-cy="contactSelect"
|
||||
></v-select>
|
||||
</v-card-text>
|
||||
|
||||
|
||||
@@ -624,7 +624,9 @@ export default {
|
||||
|
||||
let PurchasedFrom = "-";
|
||||
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 = "-";
|
||||
if (r.purchaseDate) {
|
||||
@@ -641,7 +643,7 @@ export default {
|
||||
"UnitPurchasedDate"
|
||||
)}: ${PurchasedDate}<br/>${this.$ay.t(
|
||||
"UnitReceipt"
|
||||
)}: ${r.purchaseReceiptNumber ?? "-"}`;
|
||||
)}: ${window.$gz.util.escapeHtml(r.purchaseReceiptNumber ?? "-")}`; //case 4721: escaped
|
||||
|
||||
let RecentWorkOrderList = "";
|
||||
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 chartBarControl from "./components/chart-bar-control.vue";
|
||||
import chartBarHorizontalControl from "./components/chart-bar-horizontal-control.vue";
|
||||
import vnodesControl from "./components/vnodes-control.vue";
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// LIBS AND GLOBAL ITEMS
|
||||
@@ -180,6 +181,7 @@ Vue.component("gz-attachments", attachmentControl);
|
||||
Vue.component("gz-chart-line", chartLineControl);
|
||||
Vue.component("gz-chart-bar", chartBarControl);
|
||||
Vue.component("gz-chart-bar-horizontal", chartBarHorizontalControl);
|
||||
Vue.component("gz-vnodes", vnodesControl);
|
||||
//3rd party components
|
||||
Vue.use(VueCurrencyInput);
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
class="grey lighten-2"
|
||||
:src="smallUrl"
|
||||
:alt="$ay.t('SmallLogo')"
|
||||
@load="hasSmall = true"
|
||||
@error="hasSmall = false"
|
||||
/>
|
||||
<v-file-input
|
||||
v-if="rights.change"
|
||||
v-model="uploadSmall"
|
||||
dense
|
||||
accept="image/*"
|
||||
@@ -26,20 +29,31 @@
|
||||
:rules="rules"
|
||||
data-cy="uploadSmall"
|
||||
></v-file-input>
|
||||
<v-btn color="primary" text @click="remove('small')">{{
|
||||
$ay.t("Delete")
|
||||
}}</v-btn>
|
||||
<v-btn color="primary" text @click="upload('small')">{{
|
||||
$ay.t("Upload")
|
||||
}}</v-btn>
|
||||
<v-btn
|
||||
v-if="rights.change && hasSmall"
|
||||
color="primary"
|
||||
text
|
||||
@click="remove('small')"
|
||||
>{{ $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 cols="12" class="mt-10">
|
||||
<img
|
||||
class="mt-10 grey lighten-2"
|
||||
:src="mediumUrl"
|
||||
:alt="$ay.t('MediumLogo')"
|
||||
@load="hasMedium = true"
|
||||
@error="hasMedium = false"
|
||||
/>
|
||||
<v-file-input
|
||||
v-if="rights.change"
|
||||
v-model="uploadMedium"
|
||||
dense
|
||||
accept="image/*"
|
||||
@@ -47,20 +61,31 @@
|
||||
:label="$ay.t('MediumLogo')"
|
||||
:rules="rules"
|
||||
></v-file-input>
|
||||
<v-btn color="primary" text @click="remove('medium')">{{
|
||||
$ay.t("Delete")
|
||||
}}</v-btn>
|
||||
<v-btn color="primary" text @click="upload('medium')">{{
|
||||
$ay.t("Upload")
|
||||
}}</v-btn>
|
||||
<v-btn
|
||||
v-if="rights.change && hasMedium"
|
||||
color="primary"
|
||||
text
|
||||
@click="remove('medium')"
|
||||
>{{ $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 cols="12" class="mt-10">
|
||||
<img
|
||||
class="mt-10 grey lighten-2"
|
||||
:src="largeUrl"
|
||||
:alt="$ay.t('LargeLogo')"
|
||||
@load="hasLarge = true"
|
||||
@error="hasLarge = false"
|
||||
/>
|
||||
<v-file-input
|
||||
v-if="rights.change"
|
||||
v-model="uploadLarge"
|
||||
dense
|
||||
accept="image/*"
|
||||
@@ -68,12 +93,20 @@
|
||||
:label="$ay.t('LargeLogo')"
|
||||
:rules="rules"
|
||||
></v-file-input>
|
||||
<v-btn color="primary" text @click="remove('large')">{{
|
||||
$ay.t("Delete")
|
||||
}}</v-btn>
|
||||
<v-btn color="primary" text @click="upload('large')">{{
|
||||
$ay.t("Upload")
|
||||
}}</v-btn>
|
||||
<v-btn
|
||||
v-if="rights.change && hasLarge"
|
||||
color="primary"
|
||||
text
|
||||
@click="remove('large')"
|
||||
>{{ $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-row>
|
||||
</v-form>
|
||||
@@ -94,6 +127,10 @@ export default {
|
||||
mediumUrl: null,
|
||||
largeUrl: 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: "",
|
||||
formState: {
|
||||
ready: false,
|
||||
@@ -119,7 +156,6 @@ export default {
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.formState.ready = true;
|
||||
vm.readOnly = !vm.rights.change;
|
||||
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
|
||||
generateMenu(vm);
|
||||
@@ -171,12 +207,15 @@ export default {
|
||||
switch (size) {
|
||||
case "small":
|
||||
vm.smallUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
||||
vm.uploadSmall = null;
|
||||
break;
|
||||
case "medium":
|
||||
vm.mediumUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
||||
vm.uploadMedium = null;
|
||||
break;
|
||||
case "large":
|
||||
vm.largeUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
||||
vm.uploadLarge = null;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
@@ -207,12 +246,15 @@ export default {
|
||||
switch (size) {
|
||||
case "small":
|
||||
vm.smallUrl = null;
|
||||
vm.hasSmall = false;
|
||||
break;
|
||||
case "medium":
|
||||
vm.mediumUrl = null;
|
||||
vm.hasMedium = false;
|
||||
break;
|
||||
case "large":
|
||||
vm.largeUrl = null;
|
||||
vm.hasLarge = false;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
|
||||
@@ -103,11 +103,9 @@
|
||||
<v-icon small :color="event.textColor" class="mt-n3 mr-1">{{
|
||||
iconForEvent(event.type)
|
||||
}}</v-icon>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<span
|
||||
:class="event.textColor + '--text'"
|
||||
v-html="eventSummary()"
|
||||
/><v-icon
|
||||
<span :class="event.textColor + '--text'"
|
||||
><gz-vnodes :vnodes="eventSummary()"/></span
|
||||
><v-icon
|
||||
v-if="!event.editable"
|
||||
x-small
|
||||
:color="event.textColor"
|
||||
|
||||
@@ -206,16 +206,9 @@ export default {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} 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(" ");
|
||||
for (let i = 0; i < searchTerms.length; i++) {
|
||||
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;
|
||||
item.info = highlightTerms(res.data || "", searchTerms);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
@@ -357,4 +350,39 @@ async function populateSelectionLists(vm) {
|
||||
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>
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$ay.t('BackupSetsToKeep')"
|
||||
:rules="[form().integerValid(this, 'backupSetsToKeep')]"
|
||||
:rules="[
|
||||
form().integerValid(this, 'backupSetsToKeep'),
|
||||
form().minValueValid(this, 'backupSetsToKeep', 1)
|
||||
]"
|
||||
:error-messages="form().serverErrors(this, 'backupSetsToKeep')"
|
||||
type="number"
|
||||
@input="fieldValueChanged('backupSetsToKeep')"
|
||||
|
||||
@@ -96,11 +96,9 @@
|
||||
<v-icon small :color="event.textColor" class="mr-1">{{
|
||||
iconForEvent(event.type)
|
||||
}}</v-icon>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<span
|
||||
:class="event.textColor + '--text'"
|
||||
v-html="eventSummary()"
|
||||
/><v-icon
|
||||
<span :class="event.textColor + '--text'"
|
||||
><gz-vnodes :vnodes="eventSummary()"/></span
|
||||
><v-icon
|
||||
v-if="!event.editable"
|
||||
x-small
|
||||
:color="event.textColor"
|
||||
|
||||
@@ -132,8 +132,9 @@
|
||||
|
||||
<template v-slot:event="{ event, eventSummary }">
|
||||
<div class="v-event-draggable">
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<span :class="event.textColor + '--text'" v-html="eventSummary()" />
|
||||
<span :class="event.textColor + '--text'"
|
||||
><gz-vnodes :vnodes="eventSummary()"
|
||||
/></span>
|
||||
<v-icon
|
||||
v-if="!event.editable"
|
||||
x-small
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
|
||||
|
||||
//case 4630: the year the client is built, for the copyright line (src/api/ayanova-version.js); VUE_APP_ variables
|
||||
//are compiled into the client
|
||||
process.env.VUE_APP_BUILD_YEAR = new Date().getFullYear().toString();
|
||||
|
||||
module.exports = {
|
||||
//https://medium.com/@mrodal/how-to-make-lazy-loading-actually-work-in-vue-cli-3-7f3f88cfb102
|
||||
chainWebpack: config => {
|
||||
@@ -27,6 +31,14 @@ module.exports = {
|
||||
|
||||
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: {
|
||||
//https://cli.vuejs.org/core-plugins/pwa.html#configuration
|
||||
// manifestPath: "manifest.json",
|
||||
|
||||
Reference in New Issue
Block a user