Compare commits
13 Commits
f8ee7b2da7
...
8.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 439de1500f | |||
| f2f10b819b | |||
| 44411ec165 | |||
| 66093db5fa | |||
| 04d3805fae | |||
| 44d91611a9 | |||
| 7ee5dfa119 | |||
| 74d07e77e2 | |||
| de03f56015 | |||
| 838ce9f390 | |||
| ab72022c08 | |||
| 8c3037bd86 | |||
| e4dcbd852e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ Thumbs.db
|
|||||||
# Environment
|
# Environment
|
||||||
.env.local
|
.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
|
## 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:
|
```
|
||||||
|
cd C:\data\code\raven-client\ayanova
|
||||||
Install frontend dependencies:
|
|
||||||
|
|
||||||
cd c:\data\code\raven-client\ayanova
|
|
||||||
npm ci
|
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
|
npm run serve
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compiles and minifies for production
|
## Production build
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run your tests
|
Output goes to `dist\`. The release build copies it into the server's `wwwroot`.
|
||||||
```
|
|
||||||
npm run test
|
## Lint
|
||||||
```
|
|
||||||
|
|
||||||
### Lints and fixes files
|
|
||||||
```
|
```
|
||||||
npm run lint
|
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",
|
"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."
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ export default {
|
|||||||
if (
|
if (
|
||||||
window.$gz.dev &&
|
window.$gz.dev &&
|
||||||
window.location.hostname == "localhost" &&
|
window.location.hostname == "localhost" &&
|
||||||
window.location.port == "8080"
|
(window.location.port == "8080" || window.location.port == "8081")
|
||||||
) {
|
) {
|
||||||
return "http://localhost:7575/api/v8.0/" + apiPath;
|
return "http://localhost:7575/api/v8.0/" + apiPath;
|
||||||
}
|
}
|
||||||
@@ -307,7 +307,7 @@ export default {
|
|||||||
if (
|
if (
|
||||||
window.$gz.dev &&
|
window.$gz.dev &&
|
||||||
window.location.hostname == "localhost" &&
|
window.location.hostname == "localhost" &&
|
||||||
window.location.port == "8080"
|
(window.location.port == "8080" || window.location.port == "8081")
|
||||||
) {
|
) {
|
||||||
return "http://localhost:7575/docs/";
|
return "http://localhost:7575/docs/";
|
||||||
}
|
}
|
||||||
@@ -317,7 +317,7 @@ export default {
|
|||||||
if (
|
if (
|
||||||
window.$gz.dev &&
|
window.$gz.dev &&
|
||||||
window.location.hostname == "localhost" &&
|
window.location.hostname == "localhost" &&
|
||||||
window.location.port == "8080"
|
(window.location.port == "8080" || window.location.port == "8081")
|
||||||
) {
|
) {
|
||||||
return "http://localhost:7575/cust/";
|
return "http://localhost:7575/cust/";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
v-model="selectedContact"
|
v-model="selectedContact"
|
||||||
:items="contacts"
|
:items="contacts"
|
||||||
:label="$ay.t('Contacts')"
|
:label="$ay.t('Contacts')"
|
||||||
|
data-cy="contactSelect"
|
||||||
></v-select>
|
></v-select>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
|
|||||||
@@ -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