Merge branch 'sss-objects' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2025-04-09 00:36:18 +03:00
commit aff02e393d
9 changed files with 322 additions and 235 deletions

View file

@ -8,6 +8,7 @@ import Popover from 'components/popover/popover.vue'
import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
import ModifiedIndicator from '../helpers/modified_indicator.vue'
import EmojiEditingPopover from '../helpers/emoji_editing_popover.vue'
import { useInterfaceStore } from 'src/stores/interface'
const EmojiTab = {
components: {
@ -232,7 +233,7 @@ const EmojiTab = {
})
},
displayError (msg) {
this.$store.useInterfaceStore().pushGlobalNotice({
useInterfaceStore().pushGlobalNotice({
messageKey: 'admin_dash.emoji.error',
messageArgs: [msg],
level: 'error'

View file

@ -5,6 +5,7 @@ import StringSetting from '../helpers/string_setting.vue'
import GroupSetting from '../helpers/group_setting.vue'
import Popover from 'src/components/popover/popover.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import { useInterfaceStore } from 'src/stores/interface'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@ -80,7 +81,7 @@ const FrontendsTab = {
this.$store.dispatch('loadFrontendsStuff')
if (response.error) {
const reason = await response.error.json()
this.$store.useInterfaceStore().pushGlobalNotice({
useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'admin_dash.frontend.failure_installing_frontend',
messageArgs: {
@ -90,7 +91,7 @@ const FrontendsTab = {
timeout: 5000
})
} else {
this.$store.useInterfaceStore().pushGlobalNotice({
useInterfaceStore().pushGlobalNotice({
level: 'success',
messageKey: 'admin_dash.frontend.success_installing_frontend',
messageArgs: {

View file

@ -315,7 +315,7 @@ const AppearanceTab = {
},
onImportFailure (result) {
console.error('Failure importing theme:', result)
this.$store.useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
},
importValidator (parsed, filename) {
if (filename.endsWith('.json')) {

View file

@ -1,8 +1,15 @@
import { cloneDeep } from 'lodash'
import { mapState, mapActions } from 'pinia'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { v4 as uuidv4 } from 'uuid';
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { useInterfaceStore } from 'src/stores/interface'
import {
newImporter,
newExporter
} from 'src/services/export_import/export_import.js'
import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue'
import UnitSetting from '../helpers/unit_setting.vue'
@ -13,9 +20,10 @@ import Select from 'src/components/select/select.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const SUPPORTED_TYPES = new Set(['word', 'regexp', 'user', 'user_regexp'])
const FilteringTab = {
data () {
console.log(cloneDeep(useServerSideStorageStore().prefsStorage.simple.muteFilters))
return {
replyVisibilityOptions: ['all', 'following', 'self'].map(mode => ({
key: mode,
@ -27,7 +35,44 @@ const FilteringTab = {
Object.entries(
useServerSideStorageStore().prefsStorage.simple.muteFilters
).map(([k]) => [k, false])
)
),
exportedFilter: null,
filterImporter: newImporter({
validator (parsed) {
if (Array.isArray(parsed)) return false
if (!SUPPORTED_TYPES.has(parsed.type)) return false
return true
},
onImport: (data) => {
const {
enabled = true,
expires = null,
hide = false,
name = '',
value = ''
} = data
this.createFilter({
enabled,
expires,
hide,
name,
value
})
},
onImportFailure (result) {
console.error('Failure importing filter:', result)
useInterfaceStore()
.pushGlobalNotice({
messageKey: 'settings.filter.import_failure',
level: 'error'
})
}
}),
filterExporter: newExporter({
filename: 'pleromafe_mute-filter',
getExportedObject: () => this.exportedFilter
})
}
},
components: {
@ -83,22 +128,29 @@ const FilteringTab = {
}
return valid
},
createFilter () {
const filter = {
type: 'word',
value: '',
name: 'New Filter',
enabled: true,
expires: null,
hide: false,
order: this.muteFilters.length + 2
}
createFilter (filter = {
type: 'word',
value: '',
name: 'New Filter',
enabled: true,
expires: null,
hide: false,
}) {
const newId = uuidv4()
filter.order = this.muteFilters.length + 2
this.muteFiltersDraftObject[newId] = filter
this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter })
this.pushServerSideStorage()
},
exportFilter(id) {
this.exportedFilter = { ...this.muteFiltersDraftObject[id] }
delete this.exportedFilter.order
this.filterExporter.exportData()
},
importFilter() {
this.filterImporter.importData()
},
copyFilter (id) {
const filter = { ...this.muteFiltersDraftObject[id] }
const newId = uuidv4()
@ -131,14 +183,12 @@ const FilteringTab = {
}
this.muteFiltersDraftObject[id] = filter
this.muteFiltersDraftDirty[id] = true
console.log(this.muteFiltersDraftDirty)
},
saveFilter(id) {
this.setPreference({ path: 'simple.muteFilters.' + id , value: this.muteFiltersDraftObject[id] })
this.pushServerSideStorage()
this.muteFiltersDraftDirty[id] = false
console.log(this.muteFiltersDraftDirty)
}
},
},
// Updating nested properties
watch: {

View file

@ -12,7 +12,7 @@
margin: 0.5em;
padding: 0.5em;
display: grid;
grid-template-columns: fit-content() 1fr fit-content();
grid-template-columns: fit-content 1fr fit-content;
align-items: baseline;
grid-gap: 0.5em;
}
@ -33,6 +33,7 @@
grid-column: 3;
grid-row: 1;
text-align: right;
line-height: 2;
}
.filter-field {
@ -55,5 +56,21 @@
.filter-buttons {
grid-column: 1 / span 3;
justify-self: end;
display: inline-grid;
grid-gap: 0.5em;
grid-template-columns: repeat(4, 1fr);
max-width: 100%;
justify-content: end;
}
.total {
text-align: center;
}
}
.settings-modal.-mobile .filtering-tab {
.filter-buttons {
grid-template-columns: repeat(1, 1fr);
}
}

View file

@ -73,232 +73,245 @@
<ul class="setting-list">
<h2>{{ $t('settings.filter.mute_filter') }}</h2>
<li>
<li>
<BooleanSetting path="hideFilteredStatuses">
{{ $t('settings.hide_muted_statuses') }}
</BooleanSetting>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
parent-path="hideFilteredStatuses"
:parent-invert="true"
path="hideWordFilteredPosts"
expert="1"
>
{{ $t('settings.hide_filtered_statuses') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
v-if="user"
parent-path="hideFilteredStatuses"
:parent-invert="true"
path="hideMutedThreads"
>
{{ $t('settings.hide_muted_threads') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
v-if="user"
parent-path="hideFilteredStatuses"
:parent-invert="true"
path="hideMutedPosts"
>
{{ $t('settings.hide_muted_posts') }}
</BooleanSetting>
</li>
</ul>
</li>
<li>
<BooleanSetting path="muteBotStatuses">
{{ $t('settings.mute_bot_posts') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="muteSensitiveStatuses">
{{ $t('settings.mute_sensitive_posts') }}
</BooleanSetting>
</li>
<div class="muteFilterContainer">
<div
v-for="filter in muteFiltersDraft"
:key="filter[0]"
class="mute-filter"
:style="{ order: filter[1].order }"
>
<div class="filter-name">
<label
:for="'filterName' + filter[0]"
>
{{ $t('settings.filter.name') }}
</label>
{{ ' ' }}
<BooleanSetting path="hideFilteredStatuses">
{{ $t('settings.hide_muted_statuses') }}
</BooleanSetting>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
parent-path="hideFilteredStatuses"
:parent-invert="true"
path="hideWordFilteredPosts"
expert="1"
>
{{ $t('settings.hide_filtered_statuses') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
v-if="user"
parent-path="hideFilteredStatuses"
:parent-invert="true"
path="hideMutedThreads"
>
{{ $t('settings.hide_muted_threads') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
v-if="user"
parent-path="hideFilteredStatuses"
:parent-invert="true"
path="hideMutedPosts"
>
{{ $t('settings.hide_muted_posts') }}
</BooleanSetting>
</li>
</ul>
</li>
<li>
<BooleanSetting path="muteBotStatuses">
{{ $t('settings.mute_bot_posts') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="muteSensitiveStatuses">
{{ $t('settings.mute_sensitive_posts') }}
</BooleanSetting>
</li>
<div class="muteFilterContainer">
<div
v-for="filter in muteFiltersDraft"
:key="filter[0]"
class="mute-filter"
:style="{ order: filter[1].order }"
>
<div class="filter-name">
<label
:for="'filterName' + filter[0]"
>
{{ $t('settings.filter.name') }}
</label>
{{ ' ' }}
<input
:id="'filterName' + filter[0]"
class="input"
:value="filter[1].name"
@input="updateFilter(filter[0], 'name', $event.target.value)"
>
</div>
<div class="filter-enabled">
<Checkbox
:id="'filterHide' + filter[0]"
:model-value="filter[1].hide"
:name="'filterHide' + filter[0]"
@update:model-value="updateFilter(filter[0], 'hide', $event)"
>
{{ $t('settings.filter.hide') }}
</Checkbox>
{{ ' ' }}
<Checkbox
:id="'filterEnabled' + filter[0]"
:model-value="filter[1].enabled"
:name="'filterEnabled' + filter[0]"
@update:model-value="updateFilter(filter[0], 'enabled', $event)"
>
{{ $t('settings.enabled') }}
</Checkbox>
</div>
<div class="filter-type filter-field">
<label :for="'filterType' + filter[0]">
<HelpIndicator>
<p>
{{ $t('settings.filter.help.word') }}
</p>
<p>
{{ $t('settings.filter.help.user') }}
</p>
<i18n-t
keypath="settings.filter.help.regexp"
tag="p"
>
<template #link>
<a
:href="$t('settings.filter.help.regexp_url')"
target="_blank"
>
{{ $t('settings.filter.help.regexp_link') }}
</a>
</template>
</i18n-t>
</HelpIndicator>
{{ $t('settings.filter.type') }}
</label>
<Select
:id="'filterType' + filter[0]"
class="filter-field-value"
:model-value="filter[1].type"
@update:model-value="updateFilter(filter[0], 'type', $event)"
>
<option value="word">
{{ $t('settings.filter.plain') }}
</option>
<option value="regexp">
{{ $t('settings.filter.regexp') }}
</option>
<option value="user">
{{ $t('settings.filter.user') }}
</option>
<option value="user_regexp">
{{ $t('settings.filter.user_regexp') }}
</option>
</Select>
</div>
<div class="filter-value filter-field">
<label
:for="'filterValue' + filter[0]"
>
{{ $t('settings.filter.value') }}
</label>
{{ ' ' }}
<input
:id="'filterValue' + filter[0]"
class="input filter-field-value"
:value="filter[1].value"
@input="updateFilter(filter[0], 'value', $event.target.value)"
>
</div>
<div class="filter-expires filter-field">
<label
:for="'filterExpires' + filter[0]"
>
{{ $t('settings.filter.expires') }}
</label>
{{ ' ' }}
<div class="filter-field-value">
<input
:id="'filterName' + filter[0]"
:id="'filterExpires' + filter[0]"
class="input"
:value="filter[1].name"
@input="updateFilter(filter[0], 'name', $event.target.value)"
:class="{ disabled: filter[1].expires === null }"
type="datetime-local"
:disabled="filter[1].expires === null"
:value="filter[1].expires ? getDatetimeLocal(filter[1].expires) : null"
@input="updateFilter(filter[0], 'expires', $event.target.value)"
>
</div>
<div class="filter-enabled">
<Checkbox
:id="'filterHide' + filter[0]"
:model-value="filter[1].hide"
:name="'filterHide' + filter[0]"
@update:model-value="updateFilter(filter[0], 'hide', $event)"
>
{{ $t('settings.filter.hide') }}
</Checkbox>
{{ ' ' }}
<Checkbox
:id="'filterEnabled' + filter[0]"
:model-value="filter[1].enabled"
:name="'filterEnabled' + filter[0]"
@update:model-value="updateFilter(filter[0], 'enabled', $event)"
:id="'filterExpiresNever' + filter[0]"
:model-value="filter[1].expires === null"
:name="'filterExpiresNever' + filter[0]"
class="input-inset input-boolean"
@update:model-value="updateFilter(filter[0], 'expires-never', $event)"
>
{{ $t('settings.enabled') }}
{{ $t('settings.filter.never_expires') }}
</Checkbox>
</div>
<div class="filter-type filter-field">
<label :for="'filterType' + filter[0]">
<HelpIndicator>
<p>
{{ $t('settings.filter.help.word') }}
</p>
<p>
{{ $t('settings.filter.help.user') }}
</p>
<i18n-t
keypath="settings.filter.help.regexp"
tag="p"
>
<template #link>
<a
:href="$t('settings.filter.help.regexp_url')"
target="_blank"
>
{{ $t('settings.filter.help.regexp_link') }}
</a>
</template>
</i18n-t>
</HelpIndicator>
{{ $t('settings.filter.type') }}
</label>
<Select
:id="'filterType' + filter[0]"
class="filter-field-value"
:model-value="filter[1].type"
@update:model-value="updateFilter(filter[0], 'type', $event)"
>
<option value="word">
{{ $t('settings.filter.plain') }}
</option>
<option value="regexp">
{{ $t('settings.filter.regexp') }}
</option>
<option value="user">
{{ $t('settings.filter.user') }}
</option>
<option value="user_regexp">
{{ $t('settings.filter.user_regexp') }}
</option>
</Select>
</div>
<div class="filter-value filter-field">
<label
:for="'filterValue' + filter[0]"
>
{{ $t('settings.filter.value') }}
</label>
{{ ' ' }}
<input
:id="'filterValue' + filter[0]"
class="input filter-field-value"
:value="filter[1].value"
@input="updateFilter(filter[0], 'value', $event.target.value)"
>
</div>
<div class="filter-expires filter-field">
<label
:for="'filterExpires' + filter[0]"
>
{{ $t('settings.filter.expires') }}
</label>
{{ ' ' }}
<div class="filter-field-value">
<input
:id="'filterExpires' + filter[0]"
class="input"
:class="{ disabled: filter[1].expires === null }"
type="datetime-local"
:disabled="filter[1].expires === null"
:value="filter[1].expires ? getDatetimeLocal(filter[1].expires) : null"
@input="updateFilter(filter[0], 'expires', $event.target.value)"
>
{{ ' ' }}
<Checkbox
:id="'filterExpiresNever' + filter[0]"
:model-value="filter[1].expires === null"
:name="'filterExpiresNever' + filter[0]"
class="input-inset input-boolean"
@update:model-value="updateFilter(filter[0], 'expires-never', $event)"
>
{{ $t('settings.filter.never_expires') }}
</Checkbox>
<span
v-if="filter[1].expires !== null && Date.now() > filter[1].expires"
class="alert neutral"
>
{{ $t('settings.filter.expired') }}
</span>
</div>
</div>
<div class="filter-buttons">
<span
v-if="!checkRegexValid(filter[0])"
class="alert error"
v-if="filter[1].expires !== null && Date.now() > filter[1].expires"
class="alert neutral"
>
{{ $t('settings.filter.regexp_error') }}
{{ $t('settings.filter.expired') }}
</span>
<button
class="copy-button button-default"
type="button"
@click="copyFilter(filter[0])"
>
{{ $t('settings.filter.copy') }}
</button>
{{ ' ' }}
<button
class="delete-button button-default -danger"
type="button"
@click="deleteFilter(filter[0])"
>
{{ $t('settings.filter.delete') }}
</button>
{{ ' ' }}
<button
class="save-button button-default"
:class="{ disabled: !muteFiltersDraftDirty[filter[0]] }"
:disabled="!muteFiltersDraftDirty[filter[0]]"
type="button"
@click="saveFilter(filter[0])"
>
{{ $t('settings.filter.save') }}
</button>
</div>
</div>
<div class="mute-filter">
<div
v-if="!checkRegexValid(filter[0])"
class="alert error"
>
{{ $t('settings.filter.regexp_error') }}
</div>
<div class="filter-buttons">
<button
class="add-button button-default"
class="delete-button button-default -danger"
type="button"
@click="createFilter()"
@click="deleteFilter(filter[0])"
>
{{ $t('settings.filter.new') }}
{{ $t('settings.filter.delete') }}
</button>
<button
class="export-button button-default"
type="button"
@click="exportFilter(filter[0])"
>
{{ $t('settings.filter.export') }}
</button>
<button
class="copy-button button-default"
type="button"
@click="copyFilter(filter[0])"
>
{{ $t('settings.filter.copy') }}
</button>
<button
class="save-button button-default"
:class="{ disabled: !muteFiltersDraftDirty[filter[0]] }"
:disabled="!muteFiltersDraftDirty[filter[0]]"
type="button"
@click="saveFilter(filter[0])"
>
{{ $t('settings.filter.save') }}
</button>
</div>
</div>
</li>
<div class="mute-filter new-filter">
<span class="total">
{{ $t('settings.filter.count', { count: muteFiltersDraft.length }) }}
</span>
<button
class="add-button button-default"
type="button"
@click="createFilter()"
>
{{ $t('settings.filter.new') }}
</button>
<button
class="add-button button-default"
type="button"
@click="importFilter()"
>
{{ $t('settings.filter.import') }}
</button>
</div>
</div>
</ul>
</div>
</div>

View file

@ -643,7 +643,7 @@ export default {
parser (string) { return deserialize(string) },
onImportFailure (result) {
console.error('Failure importing style:', result)
this.$store.useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
},
onImport
})

View file

@ -424,12 +424,16 @@
"value": "Value",
"expires": "Expires",
"expired": "Expired",
"copy": "Copy filter",
"save": "Save filter",
"delete": "Remove filter",
"new": "Create filter",
"copy": "Duplicate",
"save": "Save",
"delete": "Remove",
"new": "Create new",
"import": "Import",
"export": "Export",
"regexp_error": "Invalid Regular Expression",
"never_expires": "Never",
"count": "{count} filter|{count} filters",
"import_failure": "The selected file is not a supported Pleroma filter.",
"help": {
"word": "Simple and RegExp filters test against post's content and subject.",
"user": "User filter matches full user handle (user@domain) in the following: author, reply-to and mentions",

View file

@ -641,7 +641,8 @@ const users = {
declarations
.filter(x => {
return x.store === 'server-side' &&
(x.migrationNum ?? x.migrationNum > configMigration)
x.migrationNum > 0 &&
x.migrationNum > configMigration
})
.toSorted((a, b) => a.configMigration - b.configMigration)
.forEach(value => {