kb topic search + client-ip port strip + application notes html
This commit is contained in:
@@ -79,7 +79,9 @@
|
||||
<!-- Application Notes -->
|
||||
<div class="section-card" v-if="app.applicationnotes">
|
||||
<h3 class="section-title">Application Notes</h3>
|
||||
<div class="notes-text">{{ app.applicationnotes }}</div>
|
||||
<!-- Notes are authored as HTML (form says "HTML supported");
|
||||
render sanitized so scripts/handlers cannot slip in. -->
|
||||
<div class="notes-text" v-html="sanitizedNotes"></div>
|
||||
</div>
|
||||
|
||||
<!-- Versions -->
|
||||
@@ -160,10 +162,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { applicationsApi } from '@/api'
|
||||
import { getContactEmailDomain } from '@/utils/siteSettings'
|
||||
import { sanitizeNotesHtml } from '@/utils/sanitizeHtml'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -173,6 +176,9 @@ const versions = ref([])
|
||||
const installedOn = ref([])
|
||||
const contactEmailDomain = ref('')
|
||||
|
||||
// Application notes are HTML; sanitize before binding with v-html.
|
||||
const sanitizedNotes = computed(() => sanitizeNotesHtml(app.value?.applicationnotes))
|
||||
|
||||
// Build sso@domain for a contact. Assumes contact.sso and domain are set.
|
||||
function contactEmail(contact) {
|
||||
return `${contact.sso}@${contactEmailDomain.value}`
|
||||
@@ -418,7 +424,17 @@ function kbKeywords(keywords) {
|
||||
|
||||
/* Notes styling - rendered as escaped plain text, preserve author line breaks */
|
||||
.notes-text {
|
||||
white-space: pre-wrap;
|
||||
/* Rendered HTML (v-html): block tags handle spacing, so no pre-wrap. */
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.notes-text :first-child { margin-top: 0; }
|
||||
.notes-text :last-child { margin-bottom: 0; }
|
||||
.notes-text p { margin: 0 0 0.6rem; }
|
||||
.notes-text ul,
|
||||
.notes-text ol { margin: 0 0 0.6rem 1.4rem; }
|
||||
.notes-text a { color: var(--link); }
|
||||
.notes-text code,
|
||||
.notes-text pre { background: var(--bg); border-radius: 4px; padding: 0.1rem 0.3rem; }
|
||||
</style>
|
||||
|
||||
@@ -29,12 +29,19 @@ def list_articles():
|
||||
|
||||
query = KnowledgeBase.query.filter_by(isactive=True)
|
||||
|
||||
# Search
|
||||
# Search: title, keywords, and the topic (its Application's name). The topic
|
||||
# is matched via an appid subquery instead of a join so it does not collide
|
||||
# with the sort=='topic' join below; articles with no app just miss that
|
||||
# clause and still match on title/keywords.
|
||||
if search := request.args.get('search'):
|
||||
like = f'%{search}%'
|
||||
topic_appids = db.session.query(Application.appid).filter(
|
||||
Application.appname.ilike(like))
|
||||
query = query.filter(
|
||||
db.or_(
|
||||
KnowledgeBase.shortdescription.ilike(f'%{search}%'),
|
||||
KnowledgeBase.keywords.ilike(f'%{search}%')
|
||||
KnowledgeBase.shortdescription.ilike(like),
|
||||
KnowledgeBase.keywords.ilike(like),
|
||||
KnowledgeBase.appid.in_(topic_appids)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user