Skip to content

Commit c952ed1

Browse files
committed
Merge branch 'develop' into art/complaints
2 parents 1184468 + 604edb3 commit c952ed1

77 files changed

Lines changed: 1399 additions & 673 deletions

File tree

Some content is hidden

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

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ GEM
281281
puma (5.6.9)
282282
nio4r (~> 2.0)
283283
racc (1.8.1)
284-
rack (2.2.17)
284+
rack (2.2.19)
285285
rack-mini-profiler (3.3.1)
286286
rack (>= 1.2.0)
287287
rack-protection (3.2.0)

INSTALLATION.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,16 @@ Add the tags to the "Required tags" section:
233233

234234
![img/required-tags.png](img/required-tags.png)
235235

236+
## Optional: Email blocklists
237+
238+
If you want to block some known-bad actors from using the deployment, you can setup the following email blocklists:
239+
240+
- Bad email patterns for granular control over which email addresses to block.
241+
To enable it, add a file named `qpixel-email-patterns.txt` to the deployment root's parent directory.
242+
Each line in the file should be a valid regular expression that a given email will be checked against.
243+
- Bad email domains for bulk blocks on emails from known bad domains.
244+
To enable it, add a file named `qpixel-domain-blocklist.txt` to the deployment root's parent directory.
245+
Each line in the file corresponds to a domain that should be blocked.
236246

237247
## Optional: Help Topics
238248

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
document.addEventListener('DOMContentLoaded', () => {
2+
document.querySelectorAll('.js-error-type-select').forEach((el) => {
3+
$(el).select2();
4+
});
5+
});

app/assets/javascripts/qpixel_api.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -430,19 +430,34 @@ window.QPixel = {
430430
},
431431

432432
handleJSONResponse: (data, onSuccess, onFinally) => {
433-
const is_modified = data.status === 'modified';
434-
const is_success = data.status === 'success';
433+
const isFailed = data.status === 'failed';
435434

436-
if (is_modified || is_success) {
437-
onSuccess(/** @type {Parameters<typeof onSuccess>[0]} */(data));
435+
if(isFailed) {
436+
const { errors = [], message } = data;
437+
438+
if (message) {
439+
const fullMessage =
440+
errors.length > 1
441+
? `${message}:<ul>${errors.map((e) => `<li>${e.trim()}</li>`).join('')}</ul>`
442+
: errors.length === 1
443+
? `${message} (${errors[0].toLowerCase().trim()})`
444+
: message;
445+
446+
QPixel.createNotification('danger', fullMessage);
447+
}
448+
else {
449+
for (const error of errors) {
450+
QPixel.createNotification('danger', error);
451+
}
452+
}
438453
}
439454
else {
440-
QPixel.createNotification('danger', data.message);
455+
onSuccess(/** @type {Parameters<typeof onSuccess>[0]} */(data));
441456
}
442457

443458
onFinally?.(data);
444459

445-
return is_success;
460+
return !isFailed;
446461
},
447462

448463
flag: async (flag) => {

app/assets/javascripts/tags.js

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
1-
/**
2-
* @typedef {{
3-
* id: number | string
4-
* text: string
5-
* desc: string
6-
* synonyms?: string | QPixelTagSynonym[]
7-
* }} ProcessedTag
8-
*/
9-
10-
$(() => {
1+
document.addEventListener('DOMContentLoaded', () => {
112
const sum = (/** @type {number[]} */ ary) => ary.reduce((a, b) => a + b, 0);
123

134
/**
@@ -35,11 +26,11 @@ $(() => {
3526
const tagSynonyms = !!tag.synonyms ? ` <i>(${tag.synonyms})</i>` : '';
3627
const tagSpan = `<span>${tag.text}${tagSynonyms}</span>`;
3728
let desc = !!tag.desc ? splitWordsMaxLength(tag.desc, 120) : '';
38-
const descSpan = !!tag.desc ?
39-
`<br/><span class="has-color-tertiary-900 has-font-size-caption">${desc[0]}${desc.length > 1 ? '...' : ''}</span>` :
40-
'';
29+
const descSpan = !!tag.desc
30+
? `<br/><span class="has-color-tertiary-900 has-font-size-caption">${desc[0]}${desc.length > 1 ? '...' : ''}</span>`
31+
: '';
4132
return $(tagSpan + descSpan);
42-
}
33+
};
4334

4435
$('.js-tag-select').each((_i, el) => {
4536
const $tgt = $(el);
@@ -49,10 +40,10 @@ $(() => {
4940
tags: $tgt.attr('data-create') !== 'false',
5041
/**
5142
* @param {Select2.IdTextPair[]} data
52-
* @param {Select2.IdTextPair & { desc?: string }} tag
43+
* @param {Select2.IdTextPair & { desc?: string }} tag
5344
*/
5445
insertTag: function (data, tag) {
55-
tag.desc = "(Create new tag)"
46+
tag.desc = '(Create new tag)';
5647
// Insert the tag at the end of the results
5748
data.push(tag);
5849
},
@@ -66,7 +57,7 @@ $(() => {
6657
}
6758
return Object.assign(params, { tag_set: $this.data('tag-set') });
6859
},
69-
headers: { 'Accept': 'application/json' },
60+
headers: { Accept: 'application/json' },
7061
delay: 100,
7162
/**
7263
* @param {QPixelTag[]} data
@@ -80,23 +71,23 @@ $(() => {
8071
{ id: 1, text: 'hot-red-firebreather', desc: 'Very cute dragon' },
8172
{ id: 2, text: 'training', desc: 'How to train a dragon' },
8273
{ id: 3, text: 'behavior', desc: 'How a dragon behaves' },
83-
{ id: 4, text: 'sapphire-blue-waterspouter', desc: 'Other cute dragon' }
84-
]
85-
}
74+
{ id: 4, text: 'sapphire-blue-waterspouter', desc: 'Other cute dragon' },
75+
],
76+
};
8677
}
8778
return {
8879
results: data.map((t) => ({
8980
id: useIds ? t.id : t.name,
9081
text: t.name.replace(/</g, '&#x3C;').replace(/>/g, '&#x3E;'),
9182
synonyms: processSynonyms($this, t.tag_synonyms),
92-
desc: t.excerpt
93-
}))
83+
desc: t.excerpt,
84+
})),
9485
};
9586
},
9687
},
9788
placeholder: '',
9889
templateResult: template,
99-
allowClear: true
90+
allowClear: true,
10091
});
10192
});
10293

@@ -112,10 +103,13 @@ $(() => {
112103
if (synonyms.length > 3) {
113104
const searchValue = $search.data('select2').selection.$search.val().toLowerCase();
114105
displayedSynonyms = synonyms.filter((ts) => ts.name.includes(searchValue)).slice(0, 3);
115-
} else {
106+
}
107+
else {
116108
displayedSynonyms = synonyms;
117109
}
118-
let synonymsString = displayedSynonyms.map((ts) => `${ts.name.replace(/</g, '&#x3C;').replace(/>/g, '&#x3E;')}`).join(', ');
110+
let synonymsString = displayedSynonyms
111+
.map((ts) => `${ts.name.replace(/</g, '&#x3C;').replace(/>/g, '&#x3E;')}`)
112+
.join(', ');
119113
if (synonyms.length > displayedSynonyms.length) {
120114
synonymsString += `, ${synonyms.length - displayedSynonyms.length} more synonyms`;
121115
}
@@ -128,10 +122,10 @@ $(() => {
128122
const newId = parseInt(lastId, 10) + 1;
129123

130124
//Duplicate the first element at the end of the wrapper
131-
const newField = $wrapper.find('.tag-synonym[data-id="0"]')[0]
132-
.outerHTML
133-
.replace(/data-id="0"/g, 'data-id="' + newId + '"')
134-
.replace(/(?<connector>attributes(\]\[)|(_))0/g, '$<connector>' + newId)
125+
const newField = $wrapper
126+
.find('.tag-synonym[data-id="0"]')[0]
127+
.outerHTML.replace(/data-id="0"/g, 'data-id="' + newId + '"')
128+
.replace(/(?<connector>attributes(\]\[)|(_))0/g, '$<connector>' + newId);
135129
$wrapper.append(newField);
136130

137131
//Alter the newly added tag synonym
@@ -141,10 +135,10 @@ $(() => {
141135
$newTagSynonym.show();
142136

143137
//Add handler for removing an element
144-
$newTagSynonym.find(`.remove-tag-synonym`).click(removeTagSynonym);
138+
$newTagSynonym.find(`.remove-tag-synonym`).on('click', removeTagSynonym);
145139
});
146140

147-
$('.remove-tag-synonym').click(removeTagSynonym);
141+
$('.remove-tag-synonym').on('click', removeTagSynonym);
148142

149143
function removeTagSynonym() {
150144
const synonym = $(this).closest('.tag-synonym');
@@ -174,7 +168,7 @@ $(() => {
174168
const tagId = $tgt.attr('data-tag');
175169
const tagName = $tgt.attr('data-name');
176170

177-
const renameTo = prompt(`Rename tag ${tagName} to:`);
171+
const renameTo = prompt(`Rename tag "${tagName}" to:`);
178172

179173
if (!renameTo) {
180174
return;

app/assets/stylesheets/application.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,5 +308,4 @@ kbd {
308308
left: 0.25em;
309309
right: 0.25em;
310310
};
311-
vertical-align: text-bottom;
312311
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@import 'variables';
2+
3+
.error-reports-filters {
4+
5+
.form-group-horizontal .actions,
6+
.select2-container {
7+
height: 37px;
8+
margin: 4px 0px;
9+
}
10+
11+
.select2-container {
12+
.selection {
13+
.select2-selection {
14+
border-color: $muted-graphic;
15+
height: 100%;
16+
padding: 4px 0;
17+
}
18+
19+
.select2-selection__arrow {
20+
top: 50%;
21+
translate: 0 -50%;
22+
}
23+
}
24+
}
25+
26+
.form-group-horizontal {
27+
.actions {
28+
.button {
29+
margin: 0;
30+
}
31+
}
32+
}
33+
}

app/assets/stylesheets/errors.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
.error-report-summary {
2424
display: flex;
2525
align-items: center;
26-
gap: 0.15em;
26+
gap: 1em;
27+
word-break: break-word;
2728

2829
.details {
2930
flex: 1;

app/assets/stylesheets/flags.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.flags-list-header {
2+
display: flex;
3+
justify-content: space-between;
4+
}
5+
6+
.header {
7+
.header--menu {
8+
.header--item {
9+
.header--alert {
10+
min-width: 2em;
11+
padding: 0 0.5em;
12+
}
13+
}
14+
}
15+
}

app/assets/stylesheets/forms.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ select.form-element {
4747

4848
.select2-container {
4949
width: 100% !important;
50-
z-index: 8999;
5150
}
5251

5352
.select2-results__option {

0 commit comments

Comments
 (0)