@okjavis/nodebb-theme-javis 4.0.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/src/client/search.js +401 -0
- package/scss/_base.scss +51 -1
- package/scss/_buttons.scss +52 -0
- package/scss/_forms.scss +5 -3
- package/templates/feed.tpl +88 -0
- package/templates/partials/category/filter-dropdown-content.tpl +44 -0
- package/templates/partials/sidebar/user-menu-dropdown.tpl +0 -5
package/package.json
CHANGED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
define('forum/search', [
|
|
5
|
+
'search',
|
|
6
|
+
'storage',
|
|
7
|
+
'hooks',
|
|
8
|
+
'alerts',
|
|
9
|
+
'api',
|
|
10
|
+
'translator',
|
|
11
|
+
'categoryFilter',
|
|
12
|
+
'userFilter',
|
|
13
|
+
], function (searchModule, storage, hooks, alerts, api, translator, categoryFilter, userFilter) {
|
|
14
|
+
const Search = {};
|
|
15
|
+
let selectedUsers = [];
|
|
16
|
+
let selectedTags = [];
|
|
17
|
+
let selectedCids = [];
|
|
18
|
+
let searchFilters = {};
|
|
19
|
+
Search.init = function () {
|
|
20
|
+
const searchIn = $('#search-in');
|
|
21
|
+
searchIn.on('change', function () {
|
|
22
|
+
updateFormItemVisiblity(searchIn.val());
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const searchQuery = $('#results').attr('data-search-query');
|
|
26
|
+
searchModule.highlightMatches(
|
|
27
|
+
searchQuery,
|
|
28
|
+
$('.search-results .content p, .search-results .topic-title')
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
$('#advanced-search form').off('submit').on('submit', function (e) {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
searchModule.query(getSearchDataFromDOM());
|
|
34
|
+
return false;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
handleSavePreferences();
|
|
38
|
+
|
|
39
|
+
categoryFilterDropdown(ajaxify.data.selectedCids);
|
|
40
|
+
userFilterDropdown($('[component="user/filter"]'), ajaxify.data.userFilterSelected);
|
|
41
|
+
tagFilterDropdown($('[component="tag/filter"]'), ajaxify.data.tagFilterSelected);
|
|
42
|
+
|
|
43
|
+
$('[component="search/filters"]').on('hidden.bs.dropdown', '.dropdown', function () {
|
|
44
|
+
const updateFns = {
|
|
45
|
+
replies: updateReplyCountFilter,
|
|
46
|
+
time: updateTimeFilter,
|
|
47
|
+
sort: updateSortFilter,
|
|
48
|
+
tag: updateTagFilter,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (updateFns[$(this).attr('data-filter-name')]) {
|
|
52
|
+
updateFns[$(this).attr('data-filter-name')]();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const searchFiltersNew = getSearchDataFromDOM();
|
|
56
|
+
if (JSON.stringify(searchFilters) !== JSON.stringify(searchFiltersNew)) {
|
|
57
|
+
searchFilters = searchFiltersNew;
|
|
58
|
+
searchModule.query(searchFilters);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
fillOutForm();
|
|
63
|
+
updateTimeFilter();
|
|
64
|
+
updateReplyCountFilter();
|
|
65
|
+
updateSortFilter();
|
|
66
|
+
|
|
67
|
+
searchFilters = getSearchDataFromDOM();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function updateTagFilter() {
|
|
71
|
+
const isActive = selectedTags.length > 0;
|
|
72
|
+
let labelText = '[[search:tags]]';
|
|
73
|
+
if (selectedTags.length) {
|
|
74
|
+
labelText = translator.compile(
|
|
75
|
+
'search:tags-x', selectedTags.map(u => u.value).join(', ')
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
$('[component="tag/filter/button"]').toggleClass(
|
|
79
|
+
'active-filter', isActive
|
|
80
|
+
).find('.filter-label').translateHtml(labelText);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function updateTimeFilter() {
|
|
84
|
+
const isActive = $('#post-time-range').val() > 0;
|
|
85
|
+
$('#post-time-button').toggleClass(
|
|
86
|
+
'active-filter', isActive
|
|
87
|
+
).find('.filter-label').translateText(
|
|
88
|
+
isActive ?
|
|
89
|
+
`[[search:time-${$('#post-time-filter').val()}-than-${$('#post-time-range').val()}]]` :
|
|
90
|
+
`[[search:time]]`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function updateSortFilter() {
|
|
95
|
+
const isActive = $('#post-sort-by').val() !== 'relevance' || $('#post-sort-direction').val() !== 'desc';
|
|
96
|
+
$('#sort-by-button').toggleClass(
|
|
97
|
+
'active-filter', isActive
|
|
98
|
+
).find('.filter-label').translateText(
|
|
99
|
+
isActive ?
|
|
100
|
+
`[[search:sort-by-${$('#post-sort-by').val()}-${$('#post-sort-direction').val()}]]` :
|
|
101
|
+
`[[search:sort]]`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function updateReplyCountFilter() {
|
|
106
|
+
const isActive = $('#reply-count').val() > 0;
|
|
107
|
+
$('#reply-count-button').toggleClass(
|
|
108
|
+
'active-filter', isActive
|
|
109
|
+
).find('.filter-label').translateText(
|
|
110
|
+
isActive ?
|
|
111
|
+
`[[search:replies-${$('#reply-count-filter').val()}-count, ${$('#reply-count').val()}]]` :
|
|
112
|
+
`[[search:replies]]`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getSearchDataFromDOM() {
|
|
117
|
+
const form = $('#advanced-search');
|
|
118
|
+
const searchData = {
|
|
119
|
+
in: $('#search-in').val(),
|
|
120
|
+
};
|
|
121
|
+
searchData.term = $('#search-input').val();
|
|
122
|
+
if (['posts', 'titlesposts', 'titles', 'bookmarks'].includes(searchData.in)) {
|
|
123
|
+
searchData.matchWords = form.find('#match-words-filter').val();
|
|
124
|
+
searchData.by = selectedUsers.length ? selectedUsers.map(u => u.username) : undefined;
|
|
125
|
+
searchData.categories = selectedCids.length ? selectedCids : undefined;
|
|
126
|
+
searchData.searchChildren = form.find('#search-children').is(':checked');
|
|
127
|
+
searchData.hasTags = selectedTags.length ? selectedTags.map(t => t.value) : undefined;
|
|
128
|
+
searchData.replies = form.find('#reply-count').val();
|
|
129
|
+
searchData.repliesFilter = form.find('#reply-count-filter').val();
|
|
130
|
+
searchData.timeFilter = form.find('#post-time-filter').val();
|
|
131
|
+
searchData.timeRange = form.find('#post-time-range').val();
|
|
132
|
+
searchData.sortBy = form.find('#post-sort-by').val();
|
|
133
|
+
searchData.sortDirection = form.find('#post-sort-direction').val();
|
|
134
|
+
searchData.showAs = form.find('#show-results-as').val();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
hooks.fire('action:search.getSearchDataFromDOM', {
|
|
138
|
+
form: form,
|
|
139
|
+
data: searchData,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return searchData;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function updateFormItemVisiblity(searchIn) {
|
|
146
|
+
const hideTitlePostFilters = !['posts', 'titles', 'bookmarks'].some(token => searchIn.includes(token));
|
|
147
|
+
$('.post-search-item').toggleClass('hidden', hideTitlePostFilters);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function fillOutForm() {
|
|
151
|
+
const params = utils.params({
|
|
152
|
+
disableToType: true,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const searchData = searchModule.getSearchPreferences();
|
|
156
|
+
const formData = utils.merge(searchData, params);
|
|
157
|
+
|
|
158
|
+
if (formData) {
|
|
159
|
+
if (ajaxify.data.term) {
|
|
160
|
+
$('#search-input').val(ajaxify.data.term);
|
|
161
|
+
}
|
|
162
|
+
formData.in = formData.in || ajaxify.data.searchDefaultIn;
|
|
163
|
+
$('#search-in').val(formData.in);
|
|
164
|
+
updateFormItemVisiblity(formData.in);
|
|
165
|
+
|
|
166
|
+
if (formData.matchWords) {
|
|
167
|
+
$('#match-words-filter').val(formData.matchWords);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (formData.showAs) {
|
|
171
|
+
$('#show-results-as').val(formData.showAs);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (formData.by) {
|
|
175
|
+
formData.by = Array.isArray(formData.by) ? formData.by : [formData.by];
|
|
176
|
+
formData.by.forEach(function (by) {
|
|
177
|
+
$('#posted-by-user').tagsinput('add', by);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (formData.categories) {
|
|
182
|
+
$('#posted-in-categories').val(formData.categories);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (formData.searchChildren) {
|
|
186
|
+
$('#search-children').prop('checked', true);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (formData.hasTags) {
|
|
190
|
+
formData.hasTags = Array.isArray(formData.hasTags) ? formData.hasTags : [formData.hasTags];
|
|
191
|
+
formData.hasTags.forEach(function (tag) {
|
|
192
|
+
$('#has-tags').tagsinput('add', tag);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (formData.replies) {
|
|
197
|
+
$('#reply-count').val(formData.replies);
|
|
198
|
+
$('#reply-count-filter').val(formData.repliesFilter);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (formData.timeRange) {
|
|
202
|
+
$('#post-time-range').val(formData.timeRange);
|
|
203
|
+
$('#post-time-filter').val(formData.timeFilter);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (formData.sortBy || ajaxify.data.searchDefaultSortBy) {
|
|
207
|
+
$('#post-sort-by').val(formData.sortBy || ajaxify.data.searchDefaultSortBy);
|
|
208
|
+
}
|
|
209
|
+
$('#post-sort-direction').val(formData.sortDirection || 'desc');
|
|
210
|
+
|
|
211
|
+
hooks.fire('action:search.fillOutForm', {
|
|
212
|
+
form: formData,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function handleSavePreferences() {
|
|
218
|
+
$('#save-preferences').on('click', function () {
|
|
219
|
+
const data = getSearchDataFromDOM();
|
|
220
|
+
const fieldsToSave = [
|
|
221
|
+
'matchWords', 'in', 'showAs',
|
|
222
|
+
'replies', 'repliesFilter',
|
|
223
|
+
'timeFilter', 'timeRange',
|
|
224
|
+
'sortBy', 'sortDirection',
|
|
225
|
+
];
|
|
226
|
+
const saveData = {};
|
|
227
|
+
fieldsToSave.forEach((key) => {
|
|
228
|
+
saveData[key] = data[key];
|
|
229
|
+
});
|
|
230
|
+
storage.setItem('search-preferences', JSON.stringify(saveData));
|
|
231
|
+
alerts.success('[[search:search-preferences-saved]]');
|
|
232
|
+
return false;
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
$('#clear-preferences').on('click', async function () {
|
|
236
|
+
storage.removeItem('search-preferences');
|
|
237
|
+
const html = await app.parseAndTranslate('partials/search-filters', {});
|
|
238
|
+
$('[component="search/filters"]').replaceWith(html);
|
|
239
|
+
$('#search-in').val(ajaxify.data.searchDefaultIn);
|
|
240
|
+
$('#post-sort-by').val(ajaxify.data.searchDefaultSortBy);
|
|
241
|
+
$('#match-words-filter').val('all');
|
|
242
|
+
$('#show-results-as').val('posts');
|
|
243
|
+
// clearing dom removes all event handlers, reinitialize
|
|
244
|
+
userFilterDropdown($('[component="user/filter"]'), []);
|
|
245
|
+
tagFilterDropdown($('[component="tag/filter"]'), []);
|
|
246
|
+
categoryFilterDropdown([]);
|
|
247
|
+
alerts.success('[[search:search-preferences-cleared]]');
|
|
248
|
+
return false;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
function categoryFilterDropdown(_selectedCids) {
|
|
254
|
+
ajaxify.data.allCategoriesUrl = '';
|
|
255
|
+
selectedCids = _selectedCids || [];
|
|
256
|
+
const dropdownEl = $('[component="category/filter"]');
|
|
257
|
+
categoryFilter.init(dropdownEl, {
|
|
258
|
+
selectedCids: _selectedCids,
|
|
259
|
+
updateButton: false, // prevent categoryFilter module from updating the button
|
|
260
|
+
onSelect: function (data) {
|
|
261
|
+
// Update selectedCids immediately when a category is clicked
|
|
262
|
+
ajaxify.data.selectedCids = data.selectedCids;
|
|
263
|
+
selectedCids = data.selectedCids;
|
|
264
|
+
|
|
265
|
+
// Trigger search immediately
|
|
266
|
+
const searchFiltersNew = getSearchDataFromDOM();
|
|
267
|
+
if (JSON.stringify(searchFilters) !== JSON.stringify(searchFiltersNew)) {
|
|
268
|
+
searchFilters = searchFiltersNew;
|
|
269
|
+
searchModule.query(searchFilters);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
onHidden: async function (data) {
|
|
273
|
+
const isActive = data.selectedCids.length > 0 && data.selectedCids[0] !== 'all';
|
|
274
|
+
let labelText = '[[search:categories]]';
|
|
275
|
+
ajaxify.data.selectedCids = data.selectedCids;
|
|
276
|
+
selectedCids = data.selectedCids;
|
|
277
|
+
if (data.selectedCids.length === 1 && data.selectedCids[0] === 'all') {
|
|
278
|
+
ajaxify.data.selectedCategory = null;
|
|
279
|
+
} else if (data.selectedCids.length > 0) {
|
|
280
|
+
const categoryData = await api.get(`/categories/${data.selectedCids[0]}`);
|
|
281
|
+
ajaxify.data.selectedCategory = categoryData;
|
|
282
|
+
labelText = `[[search:categories-x, ${categoryData.name}]]`;
|
|
283
|
+
}
|
|
284
|
+
if (data.selectedCids.length > 1) {
|
|
285
|
+
labelText = `[[search:categories-x, ${data.selectedCids.length}]]`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
$('[component="category/filter/button"]').toggleClass(
|
|
289
|
+
'active-filter', isActive
|
|
290
|
+
).find('.filter-label').translateText(labelText);
|
|
291
|
+
},
|
|
292
|
+
localCategories: [],
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function userFilterDropdown(el, _selectedUsers) {
|
|
297
|
+
selectedUsers = _selectedUsers || [];
|
|
298
|
+
userFilter.init(el, {
|
|
299
|
+
selectedUsers: _selectedUsers,
|
|
300
|
+
template: 'partials/search-filters',
|
|
301
|
+
onSelect: function (_selectedUsers) {
|
|
302
|
+
selectedUsers = _selectedUsers;
|
|
303
|
+
},
|
|
304
|
+
onHidden: function (_selectedUsers) {
|
|
305
|
+
const isActive = _selectedUsers.length > 0;
|
|
306
|
+
let labelText = '[[search:posted-by]]';
|
|
307
|
+
if (isActive) {
|
|
308
|
+
labelText = translator.compile(
|
|
309
|
+
'search:posted-by-usernames', selectedUsers.map(u => u.username).join(', ')
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
el.find('[component="user/filter/button"]').toggleClass(
|
|
313
|
+
'active-filter', isActive
|
|
314
|
+
).find('.filter-label').translateText(labelText);
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function tagFilterDropdown(el, _selectedTags) {
|
|
320
|
+
selectedTags = _selectedTags;
|
|
321
|
+
async function renderSelectedTags() {
|
|
322
|
+
const html = await app.parseAndTranslate('partials/search-filters', 'tagFilterSelected', {
|
|
323
|
+
tagFilterSelected: selectedTags,
|
|
324
|
+
});
|
|
325
|
+
el.find('[component="tag/filter/selected"]').html(html);
|
|
326
|
+
}
|
|
327
|
+
function tagValueToObject(value) {
|
|
328
|
+
const escapedTag = utils.escapeHTML(value);
|
|
329
|
+
return {
|
|
330
|
+
value: value,
|
|
331
|
+
valueEscaped: escapedTag,
|
|
332
|
+
valueEncoded: encodeURIComponent(value),
|
|
333
|
+
class: escapedTag.replace(/\s/g, '-'),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function doSearch() {
|
|
338
|
+
let result = { tags: [] };
|
|
339
|
+
const query = el.find('[component="tag/filter/search"]').val();
|
|
340
|
+
if (query && query.length > 1) {
|
|
341
|
+
if (app.user.privileges['search:tags']) {
|
|
342
|
+
result = await socket.emit('topics.searchAndLoadTags', { query: query });
|
|
343
|
+
} else {
|
|
344
|
+
result = {
|
|
345
|
+
tags: [tagValueToObject(query)],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!result.tags.length) {
|
|
351
|
+
el.find('[component="tag/filter/results"]').translateHtml(
|
|
352
|
+
'[[tags:no-tags-found]]'
|
|
353
|
+
);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
result.tags = result.tags.slice(0, 20);
|
|
357
|
+
const tagMap = {};
|
|
358
|
+
result.tags.forEach((tag) => {
|
|
359
|
+
tagMap[tag.value] = tag;
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const html = await app.parseAndTranslate('partials/search-filters', 'tagFilterResults', {
|
|
363
|
+
tagFilterResults: result.tags,
|
|
364
|
+
});
|
|
365
|
+
el.find('[component="tag/filter/results"]').html(html);
|
|
366
|
+
el.find('[component="tag/filter/results"] [data-tag]').on('click', async function () {
|
|
367
|
+
selectedTags.push(tagMap[$(this).attr('data-tag')]);
|
|
368
|
+
renderSelectedTags();
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
el.find('[component="tag/filter/search"]').on('keyup', utils.debounce(function () {
|
|
373
|
+
if (app.user.privileges['search:tags']) {
|
|
374
|
+
doSearch();
|
|
375
|
+
}
|
|
376
|
+
}, 1000));
|
|
377
|
+
|
|
378
|
+
el.on('click', '[component="tag/filter/delete"]', function () {
|
|
379
|
+
const deleteTag = $(this).attr('data-tag');
|
|
380
|
+
selectedTags = selectedTags.filter(tag => tag.value !== deleteTag);
|
|
381
|
+
renderSelectedTags();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
el.find('[component="tag/filter/search"]').on('keyup', (e) => {
|
|
385
|
+
if (e.key === 'Enter' && !app.user.privileges['search:tags']) {
|
|
386
|
+
const value = el.find('[component="tag/filter/search"]').val();
|
|
387
|
+
if (value && selectedTags.every(tag => tag.value !== value)) {
|
|
388
|
+
selectedTags.push(tagValueToObject(value));
|
|
389
|
+
renderSelectedTags();
|
|
390
|
+
}
|
|
391
|
+
el.find('[component="tag/filter/search"]').val('');
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
el.on('shown.bs.dropdown', function () {
|
|
396
|
+
el.find('[component="tag/filter/search"]').trigger('focus');
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return Search;
|
|
401
|
+
});
|
package/scss/_base.scss
CHANGED
|
@@ -79,7 +79,8 @@ body {
|
|
|
79
79
|
font-size: $jv-font-size-base;
|
|
80
80
|
line-height: $jv-line-height-base;
|
|
81
81
|
color: $jv-text-main;
|
|
82
|
-
background
|
|
82
|
+
background: linear-gradient(180deg, #ffffff 0%, #e0f0ff 100%);
|
|
83
|
+
background-attachment: fixed;
|
|
83
84
|
-webkit-font-smoothing: antialiased;
|
|
84
85
|
-moz-osx-font-smoothing: grayscale;
|
|
85
86
|
}
|
|
@@ -144,6 +145,55 @@ a {
|
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
// ===========================================================
|
|
149
|
+
// PROFILE PAGE - Hide Push Notifications link
|
|
150
|
+
// ===========================================================
|
|
151
|
+
#web-push {
|
|
152
|
+
display: none !important;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ===========================================================
|
|
156
|
+
// PROFILE EDIT PAGE - Right side options alignment
|
|
157
|
+
// ===========================================================
|
|
158
|
+
.account .col-xl-6:last-child {
|
|
159
|
+
// Remove text-center, align left
|
|
160
|
+
text-align: left !important;
|
|
161
|
+
|
|
162
|
+
// The list group for Change Picture, Username, etc.
|
|
163
|
+
.list-group {
|
|
164
|
+
border-radius: $jv-radius-sm;
|
|
165
|
+
overflow: hidden;
|
|
166
|
+
|
|
167
|
+
.list-group-item {
|
|
168
|
+
text-align: left;
|
|
169
|
+
padding: $jv-space-4 $jv-space-6;
|
|
170
|
+
border-left: none;
|
|
171
|
+
border-right: none;
|
|
172
|
+
|
|
173
|
+
&:first-child {
|
|
174
|
+
border-top: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
a {
|
|
178
|
+
display: block;
|
|
179
|
+
font-weight: 500;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// SSO section label - align with list items above
|
|
185
|
+
> label.form-label {
|
|
186
|
+
margin-top: $jv-space-6;
|
|
187
|
+
padding-left: $jv-space-6; // Match list-group-item padding
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Delete account button container
|
|
191
|
+
.d-flex.justify-content-center {
|
|
192
|
+
justify-content: flex-start !important;
|
|
193
|
+
margin-top: $jv-space-6;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
147
197
|
// ===========================================================
|
|
148
198
|
// LOGIN PAGE ONLY - Hide search bar
|
|
149
199
|
// ===========================================================
|
package/scss/_buttons.scss
CHANGED
|
@@ -378,3 +378,55 @@
|
|
|
378
378
|
text-align: left;
|
|
379
379
|
justify-content: flex-start;
|
|
380
380
|
}
|
|
381
|
+
|
|
382
|
+
// ===========================================================
|
|
383
|
+
// LINKEDIN SSO BUTTON
|
|
384
|
+
// ===========================================================
|
|
385
|
+
.linkedin-sso-btn {
|
|
386
|
+
background-color: #0077B5 !important;
|
|
387
|
+
border-color: #0077B5 !important;
|
|
388
|
+
color: #fff !important;
|
|
389
|
+
font-size: 16px !important;
|
|
390
|
+
font-weight: 600 !important;
|
|
391
|
+
border-radius: $jv-radius-md !important;
|
|
392
|
+
box-shadow: 0 4px 14px rgba(0, 119, 181, 0.35) !important;
|
|
393
|
+
transition: all 0.2s ease !important;
|
|
394
|
+
|
|
395
|
+
&:hover {
|
|
396
|
+
background-color: #005885 !important;
|
|
397
|
+
border-color: #005885 !important;
|
|
398
|
+
box-shadow: 0 6px 20px rgba(0, 119, 181, 0.45) !important;
|
|
399
|
+
transform: translateY(-1px);
|
|
400
|
+
color: #fff !important;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
&:active {
|
|
404
|
+
transform: translateY(0);
|
|
405
|
+
box-shadow: 0 2px 8px rgba(0, 119, 181, 0.3) !important;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
i {
|
|
409
|
+
font-size: 20px !important;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// LinkedIn login block styling
|
|
414
|
+
.linkedin-login-block {
|
|
415
|
+
max-width: 400px;
|
|
416
|
+
margin: 0 auto;
|
|
417
|
+
padding: $jv-space-6;
|
|
418
|
+
background: $jv-surface;
|
|
419
|
+
border-radius: $jv-radius-lg;
|
|
420
|
+
border: 1px solid $jv-border-subtle;
|
|
421
|
+
box-shadow: $jv-shadow-md;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Hide admin login section by default (shown via JS when ?admin=true)
|
|
425
|
+
.admin-login-section {
|
|
426
|
+
display: none !important;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// When admin login is shown
|
|
430
|
+
.admin-login-section[style*="display: block"] {
|
|
431
|
+
display: block !important;
|
|
432
|
+
}
|
package/scss/_forms.scss
CHANGED
|
@@ -30,9 +30,11 @@ textarea,
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// Textarea specific
|
|
34
|
-
textarea
|
|
35
|
-
|
|
33
|
+
// Textarea specific - override pill shape for multi-line inputs
|
|
34
|
+
textarea,
|
|
35
|
+
textarea.form-control,
|
|
36
|
+
.form-control[type="textarea"] {
|
|
37
|
+
border-radius: $jv-radius-sm !important; // 8px rounded rectangle, not pill
|
|
36
38
|
resize: vertical;
|
|
37
39
|
min-height: 100px;
|
|
38
40
|
}
|
package/templates/feed.tpl
CHANGED
|
@@ -128,3 +128,91 @@ $(document).ready(function() {
|
|
|
128
128
|
});
|
|
129
129
|
});
|
|
130
130
|
</script>
|
|
131
|
+
|
|
132
|
+
<script>
|
|
133
|
+
// Embed videos on feed page - runs independently
|
|
134
|
+
(function() {
|
|
135
|
+
function embedFeedVideos() {
|
|
136
|
+
// Find all MP4 links in feed posts
|
|
137
|
+
var $links = $('a[href*=".mp4"]').not('.stretched-link');
|
|
138
|
+
|
|
139
|
+
$links.each(function() {
|
|
140
|
+
var $link = $(this);
|
|
141
|
+
|
|
142
|
+
// Avoid double-processing
|
|
143
|
+
if ($link.data('javisVideoEmbedded')) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
var href = $link.attr('href');
|
|
148
|
+
if (!href || href.indexOf('.mp4') === -1) return;
|
|
149
|
+
|
|
150
|
+
// Normalise relative URLs
|
|
151
|
+
var src = href;
|
|
152
|
+
if (href.indexOf('http') !== 0) {
|
|
153
|
+
src = window.location.origin + href;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Find the parent post content div and feed-content-section
|
|
157
|
+
var $contentDiv = $link.closest('[component="post/content"]');
|
|
158
|
+
var $feedContentSection = $link.closest('.feed-content-section');
|
|
159
|
+
|
|
160
|
+
// Remove the stretched-link overlay so video controls work
|
|
161
|
+
if ($contentDiv.length) {
|
|
162
|
+
$contentDiv.find('.stretched-link').remove();
|
|
163
|
+
$contentDiv.removeClass('position-relative');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Create the inline video player
|
|
167
|
+
var $video = $('<video />', {
|
|
168
|
+
src: src,
|
|
169
|
+
controls: true,
|
|
170
|
+
preload: 'metadata',
|
|
171
|
+
class: 'javis-feed-video'
|
|
172
|
+
}).css({
|
|
173
|
+
width: '100%',
|
|
174
|
+
maxWidth: '100%',
|
|
175
|
+
borderRadius: '8px',
|
|
176
|
+
backgroundColor: '#000'
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Wrap video in a container
|
|
180
|
+
var $videoContainer = $('<div class="feed-video-section px-3 pb-3"></div>').append($video);
|
|
181
|
+
|
|
182
|
+
// Insert video container after the feed-content-section (outside of it)
|
|
183
|
+
if ($feedContentSection.length) {
|
|
184
|
+
$feedContentSection.after($videoContainer);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Hide the link/paragraph containing the MP4 link
|
|
188
|
+
var $parent = $link.closest('p');
|
|
189
|
+
if ($parent.length) {
|
|
190
|
+
$parent.hide();
|
|
191
|
+
} else {
|
|
192
|
+
$link.hide();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
$link.data('javisVideoEmbedded', true);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Run when DOM is ready
|
|
200
|
+
if (document.readyState === 'loading') {
|
|
201
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
202
|
+
setTimeout(embedFeedVideos, 200);
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
setTimeout(embedFeedVideos, 200);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Also run on ajaxify (SPA navigation)
|
|
209
|
+
$(window).on('action:ajaxify.end', function() {
|
|
210
|
+
setTimeout(embedFeedVideos, 200);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Run when new posts are loaded (infinite scroll)
|
|
214
|
+
$(window).on('action:posts.loaded', function() {
|
|
215
|
+
setTimeout(embedFeedVideos, 200);
|
|
216
|
+
});
|
|
217
|
+
})();
|
|
218
|
+
</script>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<button type="button" class="btn btn-ghost btn-sm d-flex align-items-center ff-secondary d-flex gap-2 dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
2
|
+
{{{ if selectedCategory }}}
|
|
3
|
+
<span class="category-item d-inline-flex align-items-center gap-1">
|
|
4
|
+
{buildCategoryIcon(selectedCategory, "18px", "rounded-circle")}
|
|
5
|
+
<span class="d-none d-md-inline fw-semibold">{selectedCategory.name}</span>
|
|
6
|
+
</span>
|
|
7
|
+
{{{ else }}}
|
|
8
|
+
<i class="fa fa-fw fa-list text-primary"></i>
|
|
9
|
+
<span class="d-none d-md-inline fw-semibold">[[unread:all-categories]]</span>{{{ end }}}
|
|
10
|
+
</button>
|
|
11
|
+
|
|
12
|
+
<div class="dropdown-menu p-1">
|
|
13
|
+
<div component="category-selector-search" class="p-1 hidden">
|
|
14
|
+
<input type="text" class="form-control form-control-sm" placeholder="[[search:type-to-search]]" autocomplete="off">
|
|
15
|
+
<hr class="mt-2 mb-0"/>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<ul component="category/list" class="list-unstyled mb-0 text-sm category-dropdown-menu ghost-scrollbar" role="menu">
|
|
19
|
+
<li role="presentation" class="category" data-cid="all">
|
|
20
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" role="menuitem" href="{{{ if allCategoriesUrl }}}{config.relative_path}/{allCategoriesUrl}{{{ else }}}#{{{ end }}}">
|
|
21
|
+
<div class="flex-grow-1">[[unread:all-categories]]</div>
|
|
22
|
+
<i component="category/select/icon" class="flex-shrink-0 fa fa-fw fa-check {{{if selectedCategory}}}invisible{{{end}}}"></i>
|
|
23
|
+
</a>
|
|
24
|
+
</li>
|
|
25
|
+
{{{each categoryItems}}}
|
|
26
|
+
<li role="presentation" class="category {{{ if ./disabledClass }}}disabled{{{ end }}}" data-cid="{./cid}" data-parent-cid="{./parentCid}" data-name="{./name}">
|
|
27
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2 {{{ if ./disabledClass }}}disabled{{{ end }}}" role="menuitem" href="#">
|
|
28
|
+
{./level}
|
|
29
|
+
<span component="category-markup" class="flex-grow-1" style="{{{ if ./match }}}font-weight: bold;{{{end}}}">
|
|
30
|
+
{{{ if ./icon }}}
|
|
31
|
+
<div class="category-item d-inline-flex align-items-center gap-1">
|
|
32
|
+
{buildCategoryIcon(@value, "24px", "rounded-circle")}
|
|
33
|
+
{./name}
|
|
34
|
+
</div>
|
|
35
|
+
{{{ else }}}
|
|
36
|
+
{./name}
|
|
37
|
+
{{{ end }}}
|
|
38
|
+
</span>
|
|
39
|
+
<i component="category/select/icon" class="flex-shrink-0 fa fa-fw fa-check {{{ if !./selected }}}invisible{{{ end }}}"></i>
|
|
40
|
+
</a>
|
|
41
|
+
</li>
|
|
42
|
+
{{{end}}}
|
|
43
|
+
</ul>
|
|
44
|
+
</div>
|
|
@@ -46,11 +46,6 @@
|
|
|
46
46
|
<i class="fa fa-fw fa-edit text-secondary"></i> <span>[[user:edit-profile]]</span>
|
|
47
47
|
</a>
|
|
48
48
|
</li>
|
|
49
|
-
<li>
|
|
50
|
-
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" component="header/profilelink/settings" href="{relative_path}/user/{user.userslug}/settings" role="menuitem">
|
|
51
|
-
<i class="fa fa-fw fa-gear text-secondary"></i> <span>[[user:settings]]</span>
|
|
52
|
-
</a>
|
|
53
|
-
</li>
|
|
54
49
|
{{{ if showModMenu }}}
|
|
55
50
|
<li role="presentation" class="dropdown-divider"></li>
|
|
56
51
|
<li><h6 class="dropdown-header text-xs">[[pages:moderator-tools]]</h6></li>
|