Files
raven-client/ayanova/src/components/gzconfirm.vue
John Cardinal 66093db5fa case 4721 - client escaping of record text
Record text shown as HTML is escaped: unit warranty summary (vendor name,
receipt number), work-order address dialog (now plain text), search excerpt
(escaped, search terms highlighted as plain text, not as a regular
expression). Confirmation dialog messages are sanitised with DOMPurify.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:19:52 -07:00

140 lines
4.0 KiB
Vue

<template>
<div class="text-center">
<v-dialog
v-model="isVisible"
persistent
:max-width="maxWidth"
:width="width"
data-cy="gzconfirm"
@keydown.esc="cancel"
>
<v-card elevation="24">
<v-card-title class="text-h6 text-sm-h5 grey lighten-4">
<template v-if="options.type == 'success'">
<v-icon large color="success">$ayiCheckCircle</v-icon>
</template>
<template v-if="options.type == 'info'">
<v-icon large color="info">$ayiInfoCircle</v-icon>
</template>
<template v-if="options.type == 'question'">
<v-icon large color="info">$ayiQuestionCircle</v-icon>
</template>
<template v-if="options.type == 'warning'">
<v-icon large color="warning">$ayiExclamationCircle</v-icon>
</template>
<template v-if="options.type == 'error'">
<v-icon large color="error">$ayiExclamationTriangle</v-icon>
</template>
<template v-if="options.type == 'dire'">
<v-icon large color="error">$ayiSkullCrossbones</v-icon>
</template>
<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="safeMessage">
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
v-if="options.helpUrl"
data-cy="gzconfirm:morebutton"
text
@click="helpClick()"
>
{{ this.$root.$gz.translation.get("More") }}
</v-btn>
<v-btn
v-if="options.noButtonText"
color="primary"
text
data-cy="gzconfirm:nobutton"
@click.native="cancel"
>{{ options.noButtonText }}</v-btn
>
<v-btn
v-if="options.type == 'dire'"
class="ml-4"
color="error"
data-cy="gzconfirm:yesbutton"
@click.native="agree"
>
<v-icon left>$ayiSkullCrossbones</v-icon
>{{ options.yesButtonText }}
<v-icon right>$ayiSkullCrossbones</v-icon></v-btn
>
<v-btn
v-else
color="primary"
text
data-cy="gzconfirm:yesbutton"
@click.native="agree"
>{{ options.yesButtonText }}</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import DOMPurify from "dompurify";
export default {
data: () => ({
width: 290,
maxWidth: 290,
isVisible: false,
resolve: null,
reject: null,
options: {
title: null,
message: null,
yesButtonText: null,
noButtonText: null,
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")) {
options.message = options.message.replace(/\n/g, "<br />");
}
this.options = Object.assign(this.options, options);
this.maxWidth = Math.floor(window.innerWidth * 0.9);
const calculatedWidth = Math.floor(window.innerWidth * 0.5);
if (calculatedWidth < 290) {
this.width = 290;
} else if (calculatedWidth > 800) {
this.width = 800;
} else {
this.width = calculatedWidth;
}
this.isVisible = true;
return new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
},
agree() {
this.resolve(true);
this.isVisible = false;
},
cancel() {
this.resolve(false);
this.isVisible = false;
},
helpClick() {
window.open(this.options.helpUrl, "_blank");
}
}
};
</script>