Skip to content

Commit c762ff0

Browse files
Merge pull request #7159 from christianbeeznest/GH-3800-clean
Search: enable Xapian indexing for course resources - refs #3800
2 parents ab6f49c + 61b2f83 commit c762ff0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3878
-768
lines changed

assets/vue/components/documents/FormNewDocument.vue

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<template>
22
<form>
3+
<!-- Title -->
34
<BaseInputTextWithVuelidate
45
id="item_title"
56
v-model.trim="item.title"
67
:label="$t('Title')"
78
:vuelidate-property="v$.item.title"
89
/>
910

11+
<!-- Content editor -->
1012
<BaseTinyEditor
1113
v-if="
1214
(item.resourceNode && item.resourceNode.firstResourceFile && item.resourceNode.firstResourceFile.text) ||
@@ -18,9 +20,28 @@
1820
required
1921
/>
2022

21-
<!-- For extra content-->
23+
<!-- Advanced options: search / indexing -->
24+
<BaseAdvancedSettingsButton
25+
v-if="searchEnabled"
26+
v-model="showAdvancedSettings"
27+
>
28+
<div class="flex flex-row mb-2">
29+
<label class="font-semibold w-40">
30+
{{ $t('Options') }}:
31+
</label>
32+
<BaseCheckbox
33+
id="indexDocumentContent"
34+
v-model="item.indexDocumentContent"
35+
:label="$t('Index document content?')"
36+
name="indexDocumentContent"
37+
/>
38+
</div>
39+
</BaseAdvancedSettingsButton>
40+
41+
<!-- For extra content -->
2242
<slot></slot>
2343

44+
<!-- Submit -->
2445
<BaseButton
2546
type="primary"
2647
icon="save"
@@ -39,22 +60,35 @@ import BaseInputTextWithVuelidate from "../basecomponents/BaseInputTextWithVueli
3960
import BaseTinyEditor from "../basecomponents/BaseTinyEditor.vue"
4061
import { useI18n } from "vue-i18n"
4162
import BaseButton from "../basecomponents/BaseButton.vue"
63+
import BaseAdvancedSettingsButton from "../basecomponents/BaseAdvancedSettingsButton.vue"
64+
import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"
4265
4366
export default {
4467
name: "DocumentsForm",
45-
components: { BaseButton, BaseTinyEditor, BaseInputTextWithVuelidate },
68+
components: {
69+
BaseButton,
70+
BaseTinyEditor,
71+
BaseInputTextWithVuelidate,
72+
BaseAdvancedSettingsButton,
73+
BaseCheckbox,
74+
},
4675
props: {
4776
values: {
4877
type: Object,
4978
required: true,
5079
},
5180
errors: {
5281
type: Object,
53-
default: () => {},
82+
default: () => ({}),
5483
},
5584
initialValues: {
5685
type: Object,
57-
default: () => {},
86+
default: () => ({}),
87+
},
88+
// Indicates if full-text search is enabled at platform level
89+
searchEnabled: {
90+
type: Boolean,
91+
default: false,
5892
},
5993
},
6094
setup() {
@@ -74,19 +108,19 @@ export default {
74108
contentFile: this.initialValues ? this.initialValues.contentFile : "",
75109
parentResourceNodeId: null,
76110
resourceNode: null,
111+
showAdvancedSettings: false,
77112
}
78113
},
79114
computed: {
80115
item() {
81-
return this.initialValues || this.values
116+
// Prefer initialValues when present (edit mode), otherwise use values (create mode)
117+
return this.initialValues && Object.keys(this.initialValues).length > 0
118+
? this.initialValues
119+
: this.values
82120
},
83121
titleErrors() {
84122
const errors = []
85123
86-
/*if (!this.$v.item.title.$dirty) return errors;
87-
has(this.violations, 'title') && errors.push(this.violations.title);
88-
!this.$v.item.title.required && errors.push(this.$t('Required field'));*/
89-
90124
if (this.v$.item.title.required) {
91125
return this.$t("Required field")
92126
}
@@ -99,12 +133,13 @@ export default {
99133
},
100134
watch: {
101135
contentFile(newContent) {
102-
tinymce.get("item_content").setContent(newContent)
136+
if (window.tinymce && tinymce.get("item_content")) {
137+
tinymce.get("item_content").setContent(newContent)
138+
}
103139
},
104140
},
105141
methods: {
106142
browser(callback, value, meta) {
107-
//const route = useRoute();
108143
let nodeId = this.$route.params["node"]
109144
let folderParams = this.$route.query
110145
let url = this.$router.resolve({
@@ -113,54 +148,39 @@ export default {
113148
query: folderParams,
114149
})
115150
url = url.fullPath
116-
console.log(url)
117151
118152
if (meta.filetype === "image") {
119153
url = url + "&type=images"
120154
} else {
121155
url = url + "&type=files"
122156
}
123157
124-
console.log(url)
125-
126158
window.addEventListener("message", function (event) {
127-
var data = event.data
159+
const data = event.data
128160
if (data.url) {
129-
url = data.url
130-
console.log(meta) // {filetype: "image", fieldname: "src"}
131-
callback(url)
161+
const finalUrl = data.url
162+
callback(finalUrl)
132163
}
133164
})
134165
135166
tinymce.activeEditor.windowManager.openUrl(
136167
{
137-
url: url, // use an absolute path!
168+
url: url,
138169
title: "file manager",
139-
/*width: 900,
140-
height: 450,
141-
resizable: 'yes'*/
142170
},
143171
{
144172
oninsert: function (file, fm) {
145-
var url, info
146-
147-
// URL normalization
148-
url = fm.convAbsUrl(file.url)
149-
150-
// Make file info
151-
info = file.name + " (" + fm.formatSize(file.size) + ")"
173+
let url = fm.convAbsUrl(file.url)
174+
const info = file.name + " (" + fm.formatSize(file.size) + ")"
152175
153-
// Provide file and text for the link dialog
154176
if (meta.filetype === "file") {
155177
callback(url, { text: info, title: info })
156178
}
157179
158-
// Provide image and alt text for the image dialog
159180
if (meta.filetype === "image") {
160181
callback(url, { alt: info })
161182
}
162183
163-
// Provide alternative source and posted for the media dialog
164184
if (meta.filetype === "media") {
165185
callback(url)
166186
}
@@ -179,7 +199,7 @@ export default {
179199
required,
180200
},
181201
contentFile: {
182-
//required,
202+
// required,
183203
},
184204
parentResourceNodeId: {},
185205
resourceNode: {},

assets/vue/composables/sidebarMenu.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export function useSidebarMenu() {
1616

1717
const allowSocialTool = computed(() => platformConfigStore.getSetting("social.allow_social_tool") !== "false")
1818

19+
const allowSearchFeature = computed(() => platformConfigStore.getSetting("search.search_enabled") === "true")
20+
1921
const showTabs = computed(() => {
2022
const defaultTabs = platformConfigStore.getSetting("display.show_tabs") || []
2123
const tabsPerRoleJson = platformConfigStore.getSetting("display.show_tabs_per_role") || ""
@@ -158,6 +160,15 @@ export function useSidebarMenu() {
158160
}),
159161
)
160162

163+
// Global search (Xapian)
164+
if (allowSearchFeature.value) {
165+
items.push({
166+
icon: "mdi mdi-magnify",
167+
label: t("Search"),
168+
url: "/search/xapian/ui",
169+
})
170+
}
171+
161172
if (showTabs.value.indexOf("reporting") > -1) {
162173
const subItems = []
163174

@@ -259,9 +270,7 @@ export function useSidebarMenu() {
259270

260271
{
261272
const roles = securityStore.user?.roles || []
262-
const isQuestionManager =
263-
securityStore.isAdmin ||
264-
roles.includes("ROLE_QUESTION_MANAGER")
273+
const isQuestionManager = securityStore.isAdmin || roles.includes("ROLE_QUESTION_MANAGER")
265274

266275
if (isQuestionManager) {
267276
const questionAdminItems = [

assets/vue/views/documents/CreateFile.vue

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@
1111
@template-selected="addTemplateToEditor"
1212
/>
1313
</div>
14+
1415
<div class="documents-form-container">
1516
<DocumentsForm
1617
ref="createForm"
1718
:errors="errors"
1819
:values="item"
20+
:search-enabled="searchEnabled"
1921
@submit="onSendFormData"
2022
/>
23+
2124
<Panel
2225
v-if="$route.query.filetype === 'certificate'"
2326
:header="
@@ -30,6 +33,7 @@
3033
</Panel>
3134
</div>
3235
</div>
36+
3337
<Loading :visible="isLoading" />
3438
</template>
3539

@@ -42,6 +46,7 @@ import { RESOURCE_LINK_PUBLISHED } from "../../constants/entity/resourcelink"
4246
import Panel from "primevue/panel"
4347
import TemplateList from "../../components/documents/TemplateList.vue"
4448
import documentsService from "../../services/documents"
49+
import { usePlatformConfig } from "../../store/platformConfig"
4550
4651
const servicePrefix = "Documents"
4752
@@ -56,12 +61,28 @@ export default {
5661
Panel,
5762
},
5863
mixins: [CreateMixin],
64+
65+
// Read platform search setting once and expose as simple booleans
66+
setup() {
67+
const platformConfigStore = usePlatformConfig()
68+
69+
const raw = platformConfigStore.getSetting("search.search_enabled")
70+
const searchEnabled = raw !== "false"
71+
const defaultIndexDocumentContent = searchEnabled
72+
73+
return {
74+
searchEnabled,
75+
defaultIndexDocumentContent,
76+
}
77+
},
78+
5979
data() {
6080
const allowedFiletypes = ["file", "video", "audio", "certificate"]
6181
const filetypeQuery = this.$route.query.filetype
6282
const filetype = allowedFiletypes.includes(filetypeQuery) ? filetypeQuery : "file"
6383
6484
const finalTags = this.getCertificateTags()
85+
6586
return {
6687
item: {
6788
title: "",
@@ -70,13 +91,17 @@ export default {
7091
filetype: filetype,
7192
parentResourceNodeId: null,
7293
resourceLinkList: null,
94+
95+
// Search-related flag: default depends on global search setting
96+
indexDocumentContent: this.defaultIndexDocumentContent,
7397
},
7498
templates: [],
7599
isLoading: false,
76100
errors: {},
77101
finalTags,
78102
}
79103
},
104+
80105
created() {
81106
this.item.parentResourceNodeId = this.$route.params.node
82107
this.item.resourceLinkList = JSON.stringify([
@@ -88,27 +113,31 @@ export default {
88113
},
89114
])
90115
},
116+
91117
methods: {
92118
handleBack() {
93119
this.$router.back()
94120
},
121+
95122
addTemplateToEditor(templateContent) {
96123
this.item.contentFile = templateContent
97124
},
125+
98126
async fetchTemplates() {
99127
this.errors = {}
100128
const courseId = this.$route.query.cid
101129
try {
102-
let data = await documentsService.getTemplates(courseId)
130+
const data = await documentsService.getTemplates(courseId)
103131
this.templates = data
104132
} catch (error) {
105133
console.error(error)
106134
this.errors = error.errors
107135
}
108136
},
137+
109138
getCertificateTags() {
110139
let finalTags = ""
111-
let tags = [
140+
const tags = [
112141
"((user_firstname))",
113142
"((user_lastname))",
114143
"((user_username))",
@@ -138,12 +167,13 @@ export default {
138167
139168
return finalTags
140169
},
170+
141171
async createWithFormData(payload) {
142172
this.isLoading = true
143173
this.errors = {}
144174
try {
145-
let response = await documentsService.createWithFormData(payload)
146-
let data = await response.json()
175+
const response = await documentsService.createWithFormData(payload)
176+
const data = await response.json()
147177
console.log(data)
148178
this.onCreated(data)
149179
} catch (error) {
@@ -153,6 +183,7 @@ export default {
153183
this.isLoading = false
154184
}
155185
},
186+
156187
onCreated(item) {
157188
let message
158189
if (item["resourceNode"]) {
@@ -162,11 +193,13 @@ export default {
162193
: `${item["resourceNode"].title} created`
163194
} else {
164195
message =
165-
this.$i18n && this.$i18n.t ? this.$t("{resource} created", { resource: item.title }) : `${item.title} created`
196+
this.$i18n && this.$i18n.t
197+
? this.$t("{resource} created", { resource: item.title })
198+
: `${item.title} created`
166199
}
167200
168201
this.showMessage(message)
169-
let folderParams = this.$route.query
202+
const folderParams = this.$route.query
170203
171204
this.$router.push({
172205
name: `${this.$options.servicePrefix}List`,
@@ -175,6 +208,7 @@ export default {
175208
})
176209
},
177210
},
211+
178212
mounted() {
179213
this.fetchTemplates()
180214
},

0 commit comments

Comments
 (0)