kopia lustrzana https://github.com/nolanlawson/pinafore
255 wiersze
8.3 KiB
HTML
255 wiersze
8.3 KiB
HTML
<GenericConfirmationDialog
|
|
{id}
|
|
{label}
|
|
{title}
|
|
{positiveText}
|
|
{confirmationButtonDisabled}
|
|
on:positive="save()">
|
|
<div class="word-filter-dialog">
|
|
<div class="word-filter-keyword">
|
|
<label for="word-filter-word-or-phrase" class="word-filter-keyword-label">
|
|
<span>{intl.wordOrPhrase}</span>
|
|
<!-- no need for aria-label="Required", the input is already marked as required -->
|
|
<span aria-hidden="true" class="required">*</span>
|
|
</label>
|
|
<input type="text"
|
|
required
|
|
autocomplete="off"
|
|
autocapitalize="off"
|
|
on:input="onWordOrPhraseChange(event)"
|
|
ref:wordInput
|
|
id="word-filter-word-or-phrase"
|
|
>
|
|
</div>
|
|
<div class="word-filter-expire-after" ref:expireSelectWrapper>
|
|
<span class="word-filter-label-like word-filter-expire-label">{intl.expireAfter}</span>
|
|
<Select className="word-filter-expiry-select"
|
|
options={expiryOptions}
|
|
defaultValue={expiryDefaultValue}
|
|
on:change="onExpiryChange(event)"
|
|
label="{intl.expireAfter}"
|
|
/>
|
|
</div>
|
|
<div class="word-filter-where-to-filter">
|
|
<span class="word-filter-label-like" id="word-filter-where-to-filter-label">
|
|
<span>{intl.whereToFilter}</span>
|
|
<span aria-label="{intl.required}" class="required">*</span>
|
|
</span>
|
|
<ul class="word-filter-radio-list" aria-describedby="word-filter-where-to-filter-label" ref:contextCheckboxes>
|
|
{#each filterContexts as context}
|
|
<li>
|
|
<input type="checkbox"
|
|
name="where-to-filter"
|
|
value={context.value}
|
|
on:change="onContextChange(event)"
|
|
id="where-to-filter-{context.value}">
|
|
<label for="where-to-filter-{context.value}">{context.label}</label>
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
</div>
|
|
<div class="word-filter-irreversible">
|
|
<input type="checkbox"
|
|
name="irreversible"
|
|
ref:irreversibleCheckbox
|
|
id="word-filter-irreversible">
|
|
<label for="word-filter-irreversible">{intl.irreversible}</label>
|
|
</div>
|
|
<div class="word-filter-whole">
|
|
<input type="checkbox"
|
|
name="irreversible"
|
|
ref:wholeWordCheckbox
|
|
id="word-filter-whole">
|
|
<label for="word-filter-whole">{intl.wholeWord}</label>
|
|
</div>
|
|
</div>
|
|
</GenericConfirmationDialog>
|
|
<style>
|
|
.word-filter-dialog {
|
|
padding: 20px 40px;
|
|
overflow-y: auto;
|
|
display: grid;
|
|
grid-template-areas: "keyword expire"
|
|
"context context"
|
|
"irreversible whole";
|
|
grid-row-gap: 20px;
|
|
grid-column-gap: 10px;
|
|
}
|
|
.word-filter-label-like {
|
|
font-size: 1.3em;
|
|
}
|
|
.word-filter-radio-list {
|
|
list-style: none;
|
|
margin-top: 5px;
|
|
}
|
|
.word-filter-keyword {
|
|
grid-area: keyword;
|
|
}
|
|
.word-filter-expire-after {
|
|
grid-area: expire;
|
|
}
|
|
.word-filter-where-to-filter {
|
|
grid-area: context;
|
|
}
|
|
.word-filter-irreversible {
|
|
grid-area: irreversible;
|
|
}
|
|
.word-filter-whole {
|
|
grid-area: whole;
|
|
}
|
|
.word-filter-keyword-label, .word-filter-expire-label {
|
|
margin-right: 10px;
|
|
}
|
|
.required {
|
|
color: var(--warn-color);
|
|
}
|
|
@media (max-width: 479px) {
|
|
.word-filter-dialog {
|
|
grid-template-areas: "keyword"
|
|
"expire"
|
|
"context"
|
|
"irreversible"
|
|
"whole";
|
|
}
|
|
}
|
|
</style>
|
|
<script>
|
|
import GenericConfirmationDialog from './GenericConfirmationDialog.html'
|
|
import Select from '../../Select.html'
|
|
import { show } from '../helpers/showDialog'
|
|
import { close } from '../helpers/closeDialog'
|
|
import { oncreate as onCreateDialog } from '../helpers/onCreateDialog'
|
|
import { store } from '../../../_store/store'
|
|
import {
|
|
WORD_FILTER_CONTEXT_ACCOUNT,
|
|
WORD_FILTER_CONTEXT_HOME,
|
|
WORD_FILTER_CONTEXT_NOTIFICATIONS,
|
|
WORD_FILTER_CONTEXT_PUBLIC,
|
|
WORD_FILTER_CONTEXT_THREAD,
|
|
WORD_FILTER_CONTEXTS,
|
|
WORD_FILTER_EXPIRY_DEFAULT,
|
|
WORD_FILTER_EXPIRY_OPTIONS
|
|
} from '../../../_static/wordFilters'
|
|
import { createOrUpdateFilter } from '../../../_actions/filters'
|
|
|
|
export default {
|
|
async oncreate () {
|
|
onCreateDialog.call(this)
|
|
this.syncFilterToDom(this.get().filter)
|
|
this.computeConfirmationButtonDisabled()
|
|
},
|
|
store: () => store,
|
|
data: () => ({
|
|
filter: undefined,
|
|
instanceName: undefined,
|
|
positiveText: 'intl.save',
|
|
expiryOptions: WORD_FILTER_EXPIRY_OPTIONS,
|
|
expiryDefaultValue: WORD_FILTER_EXPIRY_DEFAULT,
|
|
filterContexts: [
|
|
{
|
|
label: 'intl.filterHome',
|
|
value: WORD_FILTER_CONTEXT_HOME
|
|
},
|
|
{
|
|
label: 'intl.filterNotifications',
|
|
value: WORD_FILTER_CONTEXT_NOTIFICATIONS
|
|
},
|
|
{
|
|
label: 'intl.filterPublic',
|
|
value: WORD_FILTER_CONTEXT_PUBLIC
|
|
},
|
|
{
|
|
label: 'intl.filterThread',
|
|
value: WORD_FILTER_CONTEXT_THREAD
|
|
},
|
|
{
|
|
label: 'intl.filterAccount',
|
|
value: WORD_FILTER_CONTEXT_ACCOUNT
|
|
}
|
|
],
|
|
confirmationButtonDisabled: false
|
|
}),
|
|
methods: {
|
|
show,
|
|
close,
|
|
onWordOrPhraseChange (event) {
|
|
this.computeConfirmationButtonDisabled()
|
|
},
|
|
onExpiryChange (event) {
|
|
|
|
},
|
|
onContextChange (event) {
|
|
this.computeConfirmationButtonDisabled()
|
|
},
|
|
async save () {
|
|
const filter = this.syncDomToFilter()
|
|
const { instanceName } = this.get()
|
|
await createOrUpdateFilter(instanceName, filter)
|
|
},
|
|
computeConfirmationButtonDisabled () {
|
|
const confirmationButtonDisabled = !(this.refs.wordInput.value.replace(/\s+/g, '') &&
|
|
[...this.refs.contextCheckboxes.querySelectorAll('input')].some(checkbox => checkbox.checked))
|
|
this.set({ confirmationButtonDisabled })
|
|
},
|
|
syncFilterToDom (filter) {
|
|
if (!filter) {
|
|
filter = {
|
|
phrase: '',
|
|
expires_at: WORD_FILTER_EXPIRY_DEFAULT,
|
|
context: [...WORD_FILTER_CONTEXTS],
|
|
irreversible: false,
|
|
whole_word: true
|
|
}
|
|
}
|
|
|
|
this.refs.wordInput.value = filter.phrase
|
|
|
|
let expiresAtValue = 0
|
|
if (filter.expires_at) {
|
|
const now = Date.now()
|
|
expiresAtValue = WORD_FILTER_EXPIRY_OPTIONS.filter(_ => _.value)
|
|
.sort((a, b) => {
|
|
// expires_at is an absolute timestamp, so sort by whichever one is closest given the current datetime
|
|
const aAbsoluteTime = now + (a.value * 1000)
|
|
const bAbsoluteTime = now + (b.value * 1000)
|
|
const aDelta = Math.abs(new Date(filter.expires_at).getTime() - aAbsoluteTime)
|
|
const bDelta = Math.abs(new Date(filter.expires_at).getTime() - bAbsoluteTime)
|
|
return aDelta < bDelta ? -1 : 1
|
|
})[0].value
|
|
}
|
|
|
|
this.refs.expireSelectWrapper.querySelector('select').value = expiresAtValue
|
|
for (const checkbox of [...this.refs.contextCheckboxes.querySelectorAll('input')]) {
|
|
checkbox.checked = filter.context.includes(checkbox.value)
|
|
}
|
|
this.refs.irreversibleCheckbox.checked = !!filter.irreversible
|
|
this.refs.wholeWordCheckbox.checked = !!filter.whole_word
|
|
},
|
|
syncDomToFilter () {
|
|
const existingFilter = this.get().filter
|
|
const filter = {
|
|
id: existingFilter && existingFilter.id
|
|
}
|
|
filter.phrase = this.refs.wordInput.value
|
|
const select = this.refs.expireSelectWrapper.querySelector('select')
|
|
const selectValue = parseInt(select.value, 10)
|
|
// When creating a new filter or updating a filter, `expires_in` is the number of seconds from now
|
|
// that the filter expires. When reading, it's `expires_at` which is a string ISO timestamp.
|
|
// Also, if you added a timeout for a filter, you can't change it to Never for some reason.
|
|
filter.expires_in = selectValue || null
|
|
filter.context = [...this.refs.contextCheckboxes.querySelectorAll('input')]
|
|
.filter(input => input.checked)
|
|
.map(input => input.value)
|
|
filter.irreversible = this.refs.irreversibleCheckbox.checked
|
|
filter.whole_word = this.refs.wholeWordCheckbox.checked
|
|
|
|
return filter
|
|
}
|
|
},
|
|
components: {
|
|
GenericConfirmationDialog,
|
|
Select
|
|
}
|
|
}
|
|
</script>
|