list and multicheckbox initial implementation

This commit is contained in:
Henry Jameson 2025-12-01 23:56:49 +02:00
commit c93f55e8f7
12 changed files with 244 additions and 139 deletions

View file

@ -4,6 +4,7 @@ import IntegerSetting from '../helpers/integer_setting.vue'
import StringSetting from '../helpers/string_setting.vue' import StringSetting from '../helpers/string_setting.vue'
import GroupSetting from '../helpers/group_setting.vue' import GroupSetting from '../helpers/group_setting.vue'
import AttachmentSetting from '../helpers/attachment_setting.vue' import AttachmentSetting from '../helpers/attachment_setting.vue'
import MultiCheckboxSetting from '../helpers/multicheckbox_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js' import SharedComputedObject from '../helpers/shared_computed_object.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -28,6 +29,7 @@ const InstanceTab = {
IntegerSetting, IntegerSetting,
StringSetting, StringSetting,
AttachmentSetting, AttachmentSetting,
MultiCheckboxSetting,
GroupSetting GroupSetting
}, },
computed: { computed: {

View file

@ -131,6 +131,19 @@
</ul> </ul>
</li> </li>
</ul> </ul>
<h3>{{ $t('admin_dash.instance.rich_metadata') }}</h3>
<ul class="setting-list">
<li>
<MultiCheckboxSetting
:path="[':pleroma','Pleroma.Web.Metadata', ':providers']"
/>
</li>
<li>
<BooleanSetting
:path="[':pleroma','Pleroma.Web.Metadata', ':unfurl_nsfw']"
/>
</li>
</ul>
</div> </div>
</div> </div>
</template> </template>

View file

@ -4,8 +4,11 @@ import IntegerSetting from '../helpers/integer_setting.vue'
import StringSetting from '../helpers/string_setting.vue' import StringSetting from '../helpers/string_setting.vue'
import GroupSetting from '../helpers/group_setting.vue' import GroupSetting from '../helpers/group_setting.vue'
import AttachmentSetting from '../helpers/attachment_setting.vue' import AttachmentSetting from '../helpers/attachment_setting.vue'
import ListSetting from '../helpers/list_setting.vue'
import MultiCheckboxSetting from '../helpers/multicheckbox_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js' import SharedComputedObject from '../helpers/shared_computed_object.js'
import { get } from 'lodash'
const MediaProxyTab = { const MediaProxyTab = {
provide () { provide () {
@ -20,9 +23,25 @@ const MediaProxyTab = {
IntegerSetting, IntegerSetting,
StringSetting, StringSetting,
AttachmentSetting, AttachmentSetting,
GroupSetting GroupSetting,
ListSetting,
MultiCheckboxSetting
}, },
computed: { computed: {
ttlSettersOptions () {
const desc = get(this.$store.state.adminSettings.descriptions, ':pleroma.:rich_media.:ttl_setters')
return new Set([...desc.suggestions, 'Pleroma.Web.RichMedia.Parser.TTL.Opengraph'].map(option => ({
label: option.replace('Pleroma.Web.RichMedia.Parser.TTL.', ''),
value: option
})))
},
parsersOptions () {
const desc = get(this.$store.state.adminSettings.descriptions, ':pleroma.:rich_media.:parsers')
return new Set(desc.suggestions.map(option => ({
label: option.replace('Pleroma.Web.RichMedia.Parsers.', ''),
value: option
})))
},
mediaProxyEnabled () { mediaProxyEnabled () {
return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][':enabled'] return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][':enabled']
}, },

View file

@ -1,115 +1,37 @@
<template> <template>
<div :label="$t('admin_dash.tabs.media_proxy')"> <div :label="$t('admin_dash.tabs.media_proxy')">
<div class="setting-item"> <div class="setting-item">
<h3>{{ $t('admin_dash.media_proxy.basic') }}</h3> <h3>{{ $t('admin_dash.links.link_previews') }}</h3>
<ul class="setting-list"> <ul class="setting-list">
<li> <li>
<BooleanSetting path=":pleroma.:media_proxy.:enabled" /> <BooleanSetting path=":pleroma.:rich_media.:enabled" />
<ul class="setting-list suboptions"> </li>
<li> <li>
<StringSetting <MultiCheckboxSetting
path=":pleroma.:media_proxy.:base_url" :override-available-options="parsersOptions"
/> path=":pleroma.:rich_media.:parsers"
</li> />
</ul> </li>
<li>
<IntegerSetting
path=":pleroma.:rich_media.:timeout"
/>
</li>
<li>
<MultiCheckboxSetting
:override-available-options="ttlSettersOptions"
path=":pleroma.:rich_media.:ttl_setters"
/>
</li>
<li>
<ListSetting path=":pleroma.:rich_media.:ignore_tld" />
</li>
<li>
<ListSetting path=":pleroma.:rich_media.:ignore_hosts" />
</li> </li>
</ul> </ul>
<template v-if="mediaProxyEnabled">
<h3>{{ $t('admin_dash.media_proxy.invalidation') }}</h3>
<ul class="setting-list">
<li>
<BooleanSetting path=":pleroma.:media_proxy.:invalidation.:enabled" />
<ul class="setting-list suboptions">
<li>
<ChoiceSetting
path=":pleroma.:media_proxy.:invalidation.:provider"
parent-path=":pleroma.:media_proxy.:invalidation.:enabled"
/>
</li>
<h4>{{ $t('admin_dash.media_proxy.invalidation_settings') }}</h4>
<ul class="setting-list suboptions">
<template v-if="mediaInvalidationProvider === 'Pleroma.Web.MediaProxy.Invalidation.Http'">
<!-- TODO: you know the drill by now - list component -->
<li>
<!-- choice maybe? -->
<StringSetting
:path="[':pleroma', 'Pleroma.Web.MediaProxy.Invalidation.Http', ':method']"
parent-path=":pleroma.:media_proxy.:invalidation.:enabled"
/>
</li>
<!-- TODO: you know the drill by now - list component AGAIN -->
</template>
<template v-if="mediaInvalidationProvider === 'Pleroma.Web.MediaProxy.Invalidation.Script'">
<!-- TODO: you know the drill by now - list component -->
<li>
<StringSetting
:path="[':pleroma', 'Pleroma.Web.MediaProxy.Invalidation.Script', ':script_path']"
parent-path=":pleroma.:media_proxy.:invalidation.:enabled"
/>
</li>
<li>
<StringSetting
:path="[':pleroma', 'Pleroma.Web.MediaProxy.Invalidation.Script', ':url_format']"
parent-path=":pleroma.:media_proxy.:invalidation.:enabled"
/>
</li>
</template>
</ul>
</ul>
</li>
</ul>
<h3>{{ $t('admin_dash.media_proxy.limits') }}</h3>
<ul class="setting-list">
<li>
<IntegerSetting
path=":pleroma.:media_proxy.:proxy_opts.:max_body_length"
/>
</li>
<li>
<IntegerSetting
path=":pleroma.:media_proxy.:proxy_opts.:max_read_duration"
/>
</li>
<li>
<BooleanSetting path=":pleroma.:media_proxy.:proxy_opts.:redirect_on_failure" />
</li>
</ul>
<!-- TODO: add whitelist when we have list component (hehe) -->
<h3>{{ $t('admin_dash.media_proxy.thumbnails') }}</h3>
<ul class="setting-list">
<li>
<BooleanSetting path=":pleroma.:media_preview_proxy.:enabled" />
<ul class="setting-list suboptions">
<li>
<IntegerSetting
parent-path=":pleroma.:media_preview_proxy.:enabled"
path=":pleroma.:media_preview_proxy.:image_quality"
/>
</li>
<li>
<IntegerSetting
parent-path=":pleroma.:media_preview_proxy.:enabled"
path=":pleroma.:media_preview_proxy.:min_content_length"
/>
</li>
<li>
<IntegerSetting
parent-path=":pleroma.:media_preview_proxy.:enabled"
path=":pleroma.:media_preview_proxy.:thumbnail_max_width"
/>
</li>
<li>
<IntegerSetting
parent-path=":pleroma.:media_preview_proxy.:enabled"
path=":pleroma.:media_preview_proxy.:thumbnail_max_height"
/>
</li>
</ul>
</li>
</ul>
</template>
</div> </div>
</div> </div>
</template> </template>
<script src="./media_proxy_tab.js"></script> <script src="./links_tab.js"></script>

View file

@ -1,13 +1,5 @@
import { isEqual } from 'lodash'
import Setting from './setting.js' import Setting from './setting.js'
export default { export default {
...Setting, ...Setting,
computed: {
...Setting.computed,
isDirty () {
return !isEqual(this.state, this.draft)
}
}
} }

View file

@ -2,6 +2,11 @@ import Setting from './setting.js'
export default { export default {
...Setting, ...Setting,
data () {
return {
newValue: ''
}
},
components: { components: {
...Setting.components ...Setting.components
}, },
@ -13,9 +18,25 @@ export default {
}, },
methods: { methods: {
...Setting.methods, ...Setting.methods,
updateValue (e) { addNew () {
console.log(e.target.value) this.update({ newValue: this.newValue })
//this.configSink(this.path, parseFloat(e.target.value) + this.stateUnit) },
getValue ({ event, index, newValue, remove }) {
if (newValue) {
this.newValue = ''
return [...this.visibleState, newValue]
} else if (remove) {
const pre = this.visibleState.slice(0, index)
const post = this.visibleState.slice(index + 1)
return [...pre, ...post]
} else {
const pre = this.visibleState.slice(0, index)
const post = this.visibleState.slice(index + 1)
const string = event?.target?.value
return [...pre, string, ...post]
}
} }
} }
} }

View file

@ -1,10 +1,9 @@
<template> <template>
<label <div
v-if="matchesExpertLevel" v-if="matchesExpertLevel"
class="StringSetting" class="ListSetting"
> >
<label <label
:for="path"
class="setting-label" class="setting-label"
:class="{ 'faint': shouldBeDisabled }" :class="{ 'faint': shouldBeDisabled }"
> >
@ -16,22 +15,6 @@
</template> </template>
<slot v-else /> <slot v-else />
</label> </label>
{{ ' ' }}
<Checkbox
v-for="option in value"
:model-value="value"
:disabled="shouldBeDisabled"
@update:model-value="update"
>
{{ value }}
</Checkbox>
{{ ' ' }}
<ModifiedIndicator
:changed="isChanged"
:onclick="reset"
/>
<ProfileSettingIndicator :is-profile="isProfileSetting" />
<DraftButtons />
<p <p
v-if="backendDescriptionDescription" v-if="backendDescriptionDescription"
class="setting-description" class="setting-description"
@ -39,7 +22,53 @@
> >
{{ backendDescriptionDescription + ' ' }} {{ backendDescriptionDescription + ' ' }}
</p> </p>
</label> <ul class="setting-list">
<li
class="btn-group"
v-for="(item, index) in visibleState"
>
<input
class="input string-input"
:class="{ disabled: shouldBeDisabled }"
:value="item"
@change="e => update({event: e, index })"
>
<button
class="button-default"
@click="e => update({ remove: true, index })"
>
<FAIcon icon="times" />
</button>
</li>
<li class="btn-group">
<input
class="input string-input"
:class="{ disabled: shouldBeDisabled }"
:disabled="shouldBeDisabled"
v-model="newValue"
>
<button
class="button-default"
@click="addNew"
>
<FAIcon icon="plus" />
</button>
</li>
</ul>
<ModifiedIndicator
:changed="isChanged"
:onclick="reset"
/>
<ProfileSettingIndicator :is-profile="isProfileSetting" />
<DraftButtons />
</div>
</template> </template>
<script src="./string_setting.js"></script> <style lang="scss">
.ListSetting {
li.btn-group {
display: flex
}
}
</style>
<script src="./list_setting.js"></script>

View file

@ -0,0 +1,47 @@
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Setting from './setting.js'
export default {
...Setting,
props: {
...Setting.props,
overrideAvailableOptions: {
required: false,
type: Set
}
},
components: {
...Setting.components,
Checkbox
},
computed: {
...Setting.computed,
availableOptions () {
if (this.overrideAvailableOptions) {
return new Set(this.overrideAvailableOptions)
}
return new Set(this.backendDescription?.suggestions.map((option) => ({
label: option,
value: option
})))
},
valueSet () {
return new Set(this.visibleState)
}
},
methods: {
...Setting.methods,
optionPresent (option) {
return this.valueSet.has(option)
},
getValue ({ event, option }) {
const set = new Set(this.visibleState)
if (event) {
set.add(option)
} else {
set.delete(option)
}
return [...set]
}
}
}

View file

@ -0,0 +1,48 @@
<template>
<label
v-if="matchesExpertLevel"
class="MultiCheckboxSetting"
>
<h5
:for="path"
class="setting-label"
:class="{ 'faint': shouldBeDisabled }"
>
<template v-if="backendDescriptionLabel">
{{ backendDescriptionLabel + ' ' }}
</template>
<template v-else-if="source === 'admin'">
MISSING LABEL FOR {{ path }}
</template>
<slot v-else />
</h5>
<p
v-if="backendDescriptionDescription"
class="setting-description"
:class="{ 'faint': shouldBeDisabled }"
>
{{ backendDescriptionDescription + ' ' }}
</p>
<ul class="setting-list">
<li v-for="option in availableOptions">
<Checkbox
:model-value="optionPresent(option.value)"
:disabled="shouldBeDisabled"
@update:model-value="e => update({event: e, option: option.value})"
>
{{ option.label }}
</Checkbox>
</li>
</ul>
<div>
<ModifiedIndicator
:changed="isChanged"
:onclick="reset"
/>
<ProfileSettingIndicator :is-profile="isProfileSetting" />
<DraftButtons />
</div>
</label>
</template>
<script src="./multicheckbox_setting.js"></script>

View file

@ -1,7 +1,7 @@
import ModifiedIndicator from './modified_indicator.vue' import ModifiedIndicator from './modified_indicator.vue'
import ProfileSettingIndicator from './profile_setting_indicator.vue' import ProfileSettingIndicator from './profile_setting_indicator.vue'
import DraftButtons from './draft_buttons.vue' import DraftButtons from './draft_buttons.vue'
import { get, set, cloneDeep } from 'lodash' import { get, set, cloneDeep, isEqual } from 'lodash'
export default { export default {
components: { components: {
@ -237,7 +237,7 @@ export default {
if (this.realSource === 'admin' && this.canonPath.length > 3) { if (this.realSource === 'admin' && this.canonPath.length > 3) {
return false // should not show draft buttons for "grouped" values return false // should not show draft buttons for "grouped" values
} else { } else {
return this.realDraftMode && this.draft !== this.state return this.realDraftMode && !isEqual(this.draft, this.state)
} }
}, },
canHardReset () { canHardReset () {

View file

@ -1,6 +1,7 @@
import VerticalTabSwitcher from './helpers/vertical_tab_switcher.jsx' import VerticalTabSwitcher from './helpers/vertical_tab_switcher.jsx'
import InstanceTab from './admin_tabs/instance_tab.vue' import InstanceTab from './admin_tabs/instance_tab.vue'
import LinksTab from './admin_tabs/links_tab.vue'
import LimitsTab from './admin_tabs/limits_tab.vue' import LimitsTab from './admin_tabs/limits_tab.vue'
import FrontendsTab from './admin_tabs/frontends_tab.vue' import FrontendsTab from './admin_tabs/frontends_tab.vue'
import MediaProxyTab from './admin_tabs/media_proxy_tab.vue' import MediaProxyTab from './admin_tabs/media_proxy_tab.vue'
@ -15,6 +16,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faWrench, faWrench,
faHand, faHand,
faChain,
faLaptopCode, faLaptopCode,
faTowerBroadcast, faTowerBroadcast,
faEnvelope, faEnvelope,
@ -26,6 +28,7 @@ import {
library.add( library.add(
faWrench, faWrench,
faHand, faHand,
faChain,
faLaptopCode, faLaptopCode,
faTowerBroadcast, faTowerBroadcast,
faEnvelope, faEnvelope,
@ -45,6 +48,7 @@ const SettingsModalAdminContent = {
MailerTab, MailerTab,
EmojiTab, EmojiTab,
UploadsTab, UploadsTab,
LinksTab,
MonitoringTab, MonitoringTab,
RegistrationsTab RegistrationsTab
}, },

View file

@ -106,6 +106,14 @@
<MediaProxyTab /> <MediaProxyTab />
</div> </div>
<div
:label="$t('admin_dash.tabs.links')"
icon="chain"
data-tab-name="links"
>
<LinksTab />
</div>
<div <div
:label="$t('admin_dash.tabs.monitoring')" :label="$t('admin_dash.tabs.monitoring')"
icon="chart-line" icon="chart-line"