forked from AkkomaGang/admin-fe
Merge branch 'master' into feature/relays-management
This commit is contained in:
commit
02ea80c756
26 changed files with 814 additions and 859 deletions
|
@ -8,7 +8,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
### Changed
|
||||
|
||||
- moves emoji pack configuration from the main menu to settings tab, redesigns it and fixes bugs
|
||||
- `mailerEnabled` must be set to `true` in order to require password reset (password reset currently only works via email)
|
||||
- remove fetching initial data for configuring server settings
|
||||
- Actions in users module (ActivateUsers, AddRight, DeactivateUsers, DeleteRight, DeleteUsers) now accept an array of users instead of one user
|
||||
|
||||
### Added
|
||||
|
||||
- Optimistic update for actions in users module and fetching users after api function finished its execution
|
||||
- Relay management
|
||||
|
||||
## [1.2.0] - 2019-09-27
|
||||
|
||||
|
|
|
@ -33,11 +33,6 @@ export async function getPasswordResetToken(nickname, authHost, token) {
|
|||
return Promise.resolve({ data: { token: 'g05lxnBJQnL', link: 'http://url/api/pleroma/password_reset/g05lxnBJQnL' }})
|
||||
}
|
||||
|
||||
export async function toggleUserActivation(nickname, authHost, token) {
|
||||
const response = users.find(user => user.nickname === nickname)
|
||||
return Promise.resolve({ data: { ...response, deactivated: !response.deactivated }})
|
||||
}
|
||||
|
||||
export async function searchUsers(query, filters, authHost, token, page = 1) {
|
||||
const filteredUsers = filterUsers(filters)
|
||||
const response = filteredUsers.filter(user => user.nickname === query)
|
||||
|
@ -48,21 +43,37 @@ export async function searchUsers(query, filters, authHost, token, page = 1) {
|
|||
}})
|
||||
}
|
||||
|
||||
export async function addRight(nickname, right, authHost, token) {
|
||||
export async function activateUsers(nicknames, authHost, token) {
|
||||
const response = nicknames.map(nickname => {
|
||||
const currentUser = users.find(user => user.nickname === nickname)
|
||||
return { ...currentUser, deactivated: false }
|
||||
})
|
||||
return Promise.resolve({ data: response })
|
||||
}
|
||||
|
||||
export async function addRight(nicknames, right, authHost, token) {
|
||||
return Promise.resolve({ data:
|
||||
{ [`is_${right}`]: true }
|
||||
})
|
||||
}
|
||||
|
||||
export async function deactivateUsers(nicknames, authHost, token) {
|
||||
const response = nicknames.map(nickname => {
|
||||
const currentUser = users.find(user => user.nickname === nickname)
|
||||
return { ...currentUser, deactivated: true }
|
||||
})
|
||||
return Promise.resolve({ data: response })
|
||||
}
|
||||
|
||||
export async function deleteRight(nickname, right, authHost, token) {
|
||||
return Promise.resolve({ data:
|
||||
{ [`is_${right}`]: false }
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteUser(nickname, authHost, token) {
|
||||
export async function deleteUsers(nicknames, authHost, token) {
|
||||
return Promise.resolve({ data:
|
||||
nickname
|
||||
nicknames
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
export const initialSettings = [
|
||||
{
|
||||
group: 'pleroma',
|
||||
key: ':instance',
|
||||
value: [
|
||||
{ 'tuple': [':name', 'Pleroma'] },
|
||||
{ 'tuple': [':email', 'example@example.com'] },
|
||||
{ 'tuple': [':notify_email', 'noreply@example.com'] },
|
||||
{ 'tuple': [':description', 'A Pleroma instance, an alternative fediverse server'] },
|
||||
{ 'tuple': [':limit', 5000] },
|
||||
{ 'tuple': [':remote_limit', 100000] },
|
||||
{ 'tuple': [':upload_limit', 16 * 1048576] },
|
||||
{ 'tuple': [':avatar_upload_limit', 2 * 1048576] },
|
||||
{ 'tuple': [':background_upload_limit', 4 * 1048576] },
|
||||
{ 'tuple': [':banner_upload_limit', 4 * 1048576] },
|
||||
{ 'tuple': [':poll_limits', [
|
||||
{ 'tuple': [':max_options', 20] },
|
||||
{ 'tuple': [':max_option_chars', 200] },
|
||||
{ 'tuple': [':min_expiration', 0] },
|
||||
{ 'tuple': [':max_expiration', 365 * 86400] }
|
||||
]] },
|
||||
{ 'tuple': [':registrations_open', true] },
|
||||
{ 'tuple': [':invites_enabled', false] },
|
||||
{ 'tuple': [':account_activation_required', false] },
|
||||
{ 'tuple': [':federating', true] },
|
||||
{ 'tuple': [':federation_reachability_timeout_days', 7] },
|
||||
{ 'tuple':
|
||||
[':federation_publisher_modules', ['Pleroma.Web.ActivityPub.Publisher', 'Pleroma.Web.Websub', 'Pleroma.Web.Salmon']] },
|
||||
{ 'tuple': [':allow_relay', true] },
|
||||
{ 'tuple': [':rewrite_policy', 'Pleroma.Web.ActivityPub.MRF.NoOpPolicy'] },
|
||||
{ 'tuple': [':public', true] },
|
||||
{ 'tuple': [':managed_config', true] },
|
||||
{ 'tuple': [':static_dir', 'instance/static/'] },
|
||||
{ 'tuple': [':allowed_post_formats', ['text/plain', 'text/html', 'text/markdown', 'text/bbcode']] },
|
||||
{ 'tuple': [':mrf_transparency', true] },
|
||||
{ 'tuple': [':extended_nickname_format', false] },
|
||||
{ 'tuple': [':max_pinned_statuses', 1] },
|
||||
{ 'tuple': [':no_attachment_links', false] },
|
||||
{ 'tuple': [':max_report_comment_size', 1000] },
|
||||
{ 'tuple': [':safe_dm_mentions', false] },
|
||||
{ 'tuple': [':healthcheck', false] },
|
||||
{ 'tuple': [':remote_post_retention_days', 90] },
|
||||
{ 'tuple': [':skip_thread_containment', true] },
|
||||
{ 'tuple': [':limit_to_local_content', ':unauthenticated'] },
|
||||
{ 'tuple': [':dynamic_configuration', true] },
|
||||
{ 'tuple': [':max_account_fields', 10] },
|
||||
{ 'tuple': [':max_remote_account_fields', 20] },
|
||||
{ 'tuple': [':account_field_name_length', 255] },
|
||||
{ 'tuple': [':account_field_value_length', 255] },
|
||||
{ 'tuple': [':external_user_synchronization', true] },
|
||||
{ 'tuple': [':user_bio_length', 5000] },
|
||||
{ 'tuple': [':user_name_length', 100] }
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'mime',
|
||||
key: ':types',
|
||||
value: {
|
||||
'application/activity+json': ['activity+json'],
|
||||
'application/jrd+json': ['jrd+json'],
|
||||
'application/ld+json': ['activity+json'],
|
||||
'application/xml': ['xml'],
|
||||
'application/xrd+xml': ['xrd+xml']
|
||||
}
|
||||
},
|
||||
{
|
||||
group: 'cors_plug',
|
||||
key: ':max_age',
|
||||
value: 86400
|
||||
},
|
||||
{
|
||||
group: 'cors_plug',
|
||||
key: ':methods',
|
||||
value: ['POST', 'PUT', 'DELETE', 'GET', 'PATCH', 'OPTIONS']
|
||||
},
|
||||
{
|
||||
group: 'cors_plug',
|
||||
key: ':expose',
|
||||
value: [
|
||||
'Link',
|
||||
'X-RateLimit-Reset',
|
||||
'X-RateLimit-Limit',
|
||||
'X-RateLimit-Remaining',
|
||||
'X-Request-Id',
|
||||
'Idempotency-Key'
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'cors_plug',
|
||||
key: ':credentials',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
group: 'cors_plug',
|
||||
key: ':headers',
|
||||
value: ['Authorization', 'Content-Type', 'Idempotency-Key']
|
||||
},
|
||||
{
|
||||
group: 'tesla',
|
||||
key: ':adapter',
|
||||
value: 'Tesla.Adapter.Hackney'
|
||||
},
|
||||
{
|
||||
group: 'pleroma',
|
||||
key: ':markup',
|
||||
value: [
|
||||
{ 'tuple': [':allow_inline_images', true] },
|
||||
{ 'tuple': [':allow_headings', false] },
|
||||
{ 'tuple': [':allow_tables', false] },
|
||||
{ 'tuple': [':allow_fonts', false] },
|
||||
{ 'tuple': [':scrub_policy', [
|
||||
'Pleroma.HTML.Transform.MediaProxy',
|
||||
'Pleroma.HTML.Scrubber.Default'
|
||||
]] }
|
||||
]
|
||||
}
|
||||
]
|
|
@ -2,12 +2,23 @@ import request from '@/utils/request'
|
|||
import { getToken } from '@/utils/auth'
|
||||
import { baseName } from './utils'
|
||||
|
||||
export async function addRight(nickname, right, authHost, token) {
|
||||
export async function activateUsers(nicknames, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/users/${nickname}/permission_group/${right}`,
|
||||
url: `/api/pleroma/admin/users/activate`,
|
||||
method: 'patch',
|
||||
headers: authHeaders(token),
|
||||
data: { nicknames }
|
||||
})
|
||||
}
|
||||
|
||||
export async function addRight(nicknames, right, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/users/permission_group/${right}`,
|
||||
method: 'post',
|
||||
headers: authHeaders(token)
|
||||
headers: authHeaders(token),
|
||||
data: { nicknames }
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -21,21 +32,33 @@ export async function createNewAccount(nickname, email, password, authHost, toke
|
|||
})
|
||||
}
|
||||
|
||||
export async function deleteRight(nickname, right, authHost, token) {
|
||||
export async function deactivateUsers(nicknames, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/users/${nickname}/permission_group/${right}`,
|
||||
method: 'delete',
|
||||
headers: authHeaders(token)
|
||||
url: `/api/pleroma/admin/users/deactivate`,
|
||||
method: 'patch',
|
||||
headers: authHeaders(token),
|
||||
data: { nicknames }
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteUser(nickname, authHost, token) {
|
||||
export async function deleteRight(nicknames, right, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/users?nickname=${nickname}`,
|
||||
url: `/api/pleroma/admin/users/permission_group/${right}`,
|
||||
method: 'delete',
|
||||
headers: authHeaders(token)
|
||||
headers: authHeaders(token),
|
||||
data: { nicknames }
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteUsers(nicknames, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/users`,
|
||||
method: 'delete',
|
||||
headers: authHeaders(token),
|
||||
data: { nicknames }
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -94,15 +117,6 @@ export async function tagUser(nicknames, tags, authHost, token) {
|
|||
})
|
||||
}
|
||||
|
||||
export async function toggleUserActivation(nickname, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/users/${nickname}/toggle_activation`,
|
||||
method: 'patch',
|
||||
headers: authHeaders(token)
|
||||
})
|
||||
}
|
||||
|
||||
export async function untagUser(nicknames, tags, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
|
|
|
@ -313,7 +313,56 @@ export default {
|
|||
database: 'Database',
|
||||
other: 'Other',
|
||||
relays: 'Relays',
|
||||
success: 'Settings changed successfully!'
|
||||
success: 'Settings changed successfully!',
|
||||
emojiPacks: 'Emoji packs',
|
||||
reloadEmoji: 'Reload emoji',
|
||||
importPacks: 'Import packs from the server filesystem',
|
||||
importEmojiTooltip: 'Importing from the filesystem will scan the directories and import those without pack.json but with emoji.txt or without neither',
|
||||
localPacks: 'Local packs',
|
||||
refreshLocalPacks: 'Refresh local packs',
|
||||
createLocalPack: 'Create a new local pack',
|
||||
packs: 'Packs',
|
||||
remotePacks: 'Remote packs',
|
||||
remoteInstanceAddress: 'Remote instance address',
|
||||
refreshRemote: 'Refresh remote packs',
|
||||
sharePack: 'Share pack',
|
||||
homepage: 'Homepage',
|
||||
description: 'Description',
|
||||
license: 'License',
|
||||
fallbackSrc: 'Fallback source',
|
||||
fallbackSrcSha: 'Fallback source SHA',
|
||||
savePackMetadata: 'Save pack metadata',
|
||||
addNewEmoji: 'Add new emoji to the pack',
|
||||
shortcode: 'Shortcode',
|
||||
uploadFile: 'Upload a file',
|
||||
customFilename: 'Custom filename',
|
||||
optional: 'optional',
|
||||
customFilenameDesc: 'Custom file name (optional)',
|
||||
url: 'URL',
|
||||
required: 'required',
|
||||
clickToUpload: 'Click to upload',
|
||||
showPackContents: 'Show pack contents',
|
||||
manageEmoji: 'Manage existing emoji',
|
||||
file: 'File',
|
||||
update: 'Update',
|
||||
remove: 'Remove',
|
||||
selectLocalPack: 'Select the local pack to copy to',
|
||||
localPack: 'Local pack',
|
||||
specifyShortcode: 'Specify a custom shortcode',
|
||||
specifyFilename: 'Specify a custom filename',
|
||||
leaveEmptyShortcode: 'leave empty to use the same shortcode',
|
||||
leaveEmptyFilename: 'leave empty to use the same filename',
|
||||
copy: 'Copy',
|
||||
copyToLocalPack: 'Copy to local pack',
|
||||
thisWillDownload: 'This will download the',
|
||||
downloadToCurrentInstance: 'pack to the current instance under the name',
|
||||
canBeChanged: 'can be changed below',
|
||||
willBeUsable: 'It will then be usable and shareable from the current instance',
|
||||
downloadPack: 'Download pack',
|
||||
deletePack: 'Delete pack',
|
||||
downloadSharedPack: 'Download shared pack to current instance',
|
||||
downloadAsOptional: 'Download as (optional)',
|
||||
downloadPackArchive: 'Download pack archive'
|
||||
},
|
||||
invites: {
|
||||
inviteTokens: 'Invite tokens',
|
||||
|
|
|
@ -63,20 +63,6 @@ const moderationLog = {
|
|||
]
|
||||
}
|
||||
|
||||
const emojiPacksDisabled = disabledFeatures.includes('emoji-packs')
|
||||
const emojiPacks = {
|
||||
path: '/emoji-packs',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: () => import('@/views/emoji-packs/index'),
|
||||
name: 'Emoji packs',
|
||||
meta: { title: 'emoji-packs', icon: 'settings', noCache: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const constantRouterMap = [
|
||||
{
|
||||
path: '/redirect',
|
||||
|
@ -144,7 +130,6 @@ export const asyncRouterMap = [
|
|||
...(invitesDisabled ? [] : [invites]),
|
||||
...(moderationLogDisabled ? [] : [moderationLog]),
|
||||
...(settingsDisabled ? [] : [settings]),
|
||||
...(emojiPacksDisabled ? [] : [emojiPacks]),
|
||||
{
|
||||
path: '/users/:id',
|
||||
component: Layout,
|
||||
|
|
|
@ -13,7 +13,7 @@ import user from './modules/user'
|
|||
import userProfile from './modules/userProfile'
|
||||
import users from './modules/users'
|
||||
import getters from './getters'
|
||||
import emoji_packs from './modules/emoji_packs.js'
|
||||
import emojiPacks from './modules/emojiPacks.js'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
|
@ -31,7 +31,7 @@ const store = new Vuex.Store({
|
|||
user,
|
||||
userProfile,
|
||||
users,
|
||||
emoji_packs
|
||||
emojiPacks
|
||||
},
|
||||
getters
|
||||
})
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
deletePack,
|
||||
savePackMetadata,
|
||||
importFromFS,
|
||||
updatePackFile } from '@/api/emoji_packs'
|
||||
updatePackFile } from '@/api/emojiPacks'
|
||||
|
||||
import { Message } from 'element-ui'
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { fetchSettings, updateSettings, uploadMedia } from '@/api/settings'
|
||||
import { initialSettings } from '@/api/initialDataForConfig'
|
||||
import { filterIgnored, parseTuples, valueHasTuples, wrapConfig } from './normalizers'
|
||||
|
||||
const settings = {
|
||||
|
@ -116,11 +115,7 @@ const settings = {
|
|||
async FetchSettings({ commit, dispatch, getters }) {
|
||||
commit('SET_LOADING', true)
|
||||
const response = await fetchSettings(getters.authHost, getters.token)
|
||||
if (response.data.configs.length === 0) {
|
||||
dispatch('SubmitChanges', initialSettings)
|
||||
} else {
|
||||
commit('SET_SETTINGS', response.data.configs)
|
||||
}
|
||||
commit('SET_SETTINGS', response.data.configs)
|
||||
commit('SET_LOADING', false)
|
||||
},
|
||||
RewriteConfig({ commit }, { tab, data }) {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import {
|
||||
activateUsers,
|
||||
addRight,
|
||||
createNewAccount,
|
||||
deactivateUsers,
|
||||
deleteRight,
|
||||
deleteUser,
|
||||
deleteUsers,
|
||||
fetchUsers,
|
||||
getPasswordResetToken,
|
||||
searchUsers,
|
||||
tagUser,
|
||||
toggleUserActivation,
|
||||
untagUser,
|
||||
requirePasswordReset
|
||||
} from '@/api/users'
|
||||
|
@ -37,12 +38,6 @@ const users = {
|
|||
SET_LOADING: (state, status) => {
|
||||
state.loading = status
|
||||
},
|
||||
SWAP_USER: (state, updatedUser) => {
|
||||
const updated = state.fetchedUsers.map(user => user.id === updatedUser.id ? updatedUser : user)
|
||||
state.fetchedUsers = updated
|
||||
.map(user => user.nickname ? user : { ...user, nickname: '' })
|
||||
.sort((a, b) => a.nickname.localeCompare(b.nickname))
|
||||
},
|
||||
SWAP_USERS: (state, users) => {
|
||||
const usersWithoutSwapped = users.reduce((acc, user) => {
|
||||
return acc.filter(u => u.id !== user.id)
|
||||
|
@ -76,11 +71,35 @@ const users = {
|
|||
}
|
||||
},
|
||||
actions: {
|
||||
async AddTag({ commit, getters }, { users, tag }) {
|
||||
async ActivateUsers({ commit, dispatch, getters, state }, users) {
|
||||
const updatedUsers = users.map(user => {
|
||||
return { ...user, deactivated: false }
|
||||
})
|
||||
commit('SWAP_USERS', updatedUsers)
|
||||
|
||||
const usersNicknames = users.map(user => user.nickname)
|
||||
await activateUsers(usersNicknames, getters.authHost, getters.token)
|
||||
dispatch('FetchUsers', { page: state.currentPage })
|
||||
},
|
||||
async AddRight({ commit, dispatch, getters, state }, { users, right }) {
|
||||
const updatedUsers = users.map(user => {
|
||||
return user.local ? { ...user, roles: { ...user.roles, [right]: true }} : user
|
||||
})
|
||||
commit('SWAP_USERS', updatedUsers)
|
||||
|
||||
const usersNicknames = users.map(user => user.nickname)
|
||||
await addRight(usersNicknames, right, getters.authHost, getters.token)
|
||||
dispatch('FetchUsers', { page: state.currentPage })
|
||||
},
|
||||
async AddTag({ commit, dispatch, getters, state }, { users, tag }) {
|
||||
const updatedUsers = users.map(user => {
|
||||
return { ...user, tags: [...user.tags, tag] }
|
||||
})
|
||||
commit('SWAP_USERS', updatedUsers)
|
||||
|
||||
const nicknames = users.map(user => user.nickname)
|
||||
await tagUser(nicknames, [tag], getters.authHost, getters.token)
|
||||
|
||||
commit('SWAP_USERS', users.map(user => ({ ...user, tags: [...user.tags, tag] })))
|
||||
dispatch('FetchUsers', { page: state.currentPage })
|
||||
},
|
||||
async ClearFilters({ commit, dispatch, state }) {
|
||||
commit('CLEAR_USERS_FILTERS')
|
||||
|
@ -90,33 +109,60 @@ const users = {
|
|||
await createNewAccount(nickname, email, password, getters.authHost, getters.token)
|
||||
dispatch('FetchUsers', { page: state.currentPage })
|
||||
},
|
||||
async DeleteUser({ commit, getters, state }, user) {
|
||||
const { data } = await deleteUser(user.nickname, getters.authHost, getters.token)
|
||||
const users = state.fetchedUsers.filter(user => user.nickname !== data)
|
||||
commit('SET_USERS', users)
|
||||
async DeactivateUsers({ commit, dispatch, getters, state }, users) {
|
||||
const updatedUsers = users.map(user => {
|
||||
return { ...user, deactivated: true }
|
||||
})
|
||||
commit('SWAP_USERS', updatedUsers)
|
||||
|
||||
const usersNicknames = users.map(user => user.nickname)
|
||||
await deactivateUsers(usersNicknames, getters.authHost, getters.token)
|
||||
dispatch('FetchUsers', { page: state.currentPage })
|
||||
},
|
||||
async RequirePasswordReset({ commit, getters, state }, user) {
|
||||
async DeleteRight({ commit, dispatch, getters, state }, { users, right }) {
|
||||
const updatedUsers = users.map(user => {
|
||||
return user.local ? { ...user, roles: { ...user.roles, [right]: false }} : user
|
||||
})
|
||||
commit('SWAP_USERS', updatedUsers)
|
||||
|
||||
const usersNicknames = users.map(user => user.nickname)
|
||||
await deleteRight(usersNicknames, right, getters.authHost, getters.token)
|
||||
dispatch('FetchUsers', { page: state.currentPage })
|
||||
},
|
||||
async DeleteUsers({ commit, getters, state }, users) {
|
||||
const deletedUsersIds = users.map(deletedUser => deletedUser.id)
|
||||
const updatedUsers = state.fetchedUsers.filter(user => !deletedUsersIds.includes(user.id))
|
||||
commit('SET_USERS', updatedUsers)
|
||||
|
||||
const usersNicknames = users.map(user => user.nickname)
|
||||
await deleteUsers(usersNicknames, getters.authHost, getters.token)
|
||||
},
|
||||
async RequirePasswordReset({ getters }, user) {
|
||||
await requirePasswordReset(user.nickname, getters.authHost, getters.token)
|
||||
},
|
||||
async FetchUsers({ commit, state, getters, dispatch }, { page }) {
|
||||
async FetchUsers({ commit, dispatch, getters, state }, { page }) {
|
||||
commit('SET_LOADING', true)
|
||||
const filters = Object.keys(state.filters).filter(filter => state.filters[filter]).join()
|
||||
const response = await fetchUsers(filters, getters.authHost, getters.token, page)
|
||||
await dispatch('GetNodeInfo')
|
||||
loadUsers(commit, page, response.data)
|
||||
},
|
||||
async GetPasswordResetToken({ commit, state, getters }, nickname) {
|
||||
async GetPasswordResetToken({ commit, getters }, nickname) {
|
||||
const { data } = await getPasswordResetToken(nickname, getters.authHost, getters.token)
|
||||
commit('SET_PASSWORD_RESET_TOKEN', data)
|
||||
},
|
||||
RemovePasswordToken({ commit }) {
|
||||
commit('SET_PASSWORD_RESET_TOKEN', { link: '', token: '' })
|
||||
},
|
||||
async RemoveTag({ commit, getters }, { users, tag }) {
|
||||
async RemoveTag({ commit, dispatch, getters, state }, { users, tag }) {
|
||||
const updatedUsers = users.map(user => {
|
||||
return { ...user, tags: user.tags.filter(userTag => userTag !== tag) }
|
||||
})
|
||||
commit('SWAP_USERS', updatedUsers)
|
||||
|
||||
const nicknames = users.map(user => user.nickname)
|
||||
await untagUser(nicknames, [tag], getters.authHost, getters.token)
|
||||
|
||||
commit('SWAP_USERS', users.map(user => ({ ...user, tags: user.tags.filter(userTag => userTag !== tag) })))
|
||||
dispatch('FetchUsers', { page: state.currentPage })
|
||||
},
|
||||
async SearchUsers({ commit, dispatch, state, getters }, { query, page }) {
|
||||
if (query.length === 0) {
|
||||
|
@ -132,10 +178,6 @@ const users = {
|
|||
loadUsers(commit, page, response.data)
|
||||
}
|
||||
},
|
||||
async ToggleUserActivation({ commit, getters }, nickname) {
|
||||
const { data } = await toggleUserActivation(nickname, getters.authHost, getters.token)
|
||||
commit('SWAP_USER', data)
|
||||
},
|
||||
async ToggleUsersFilter({ commit, dispatch, state }, filters) {
|
||||
const defaultFilters = {
|
||||
local: false,
|
||||
|
@ -146,14 +188,6 @@ const users = {
|
|||
const currentFilters = { ...defaultFilters, ...filters }
|
||||
commit('SET_USERS_FILTERS', currentFilters)
|
||||
dispatch('SearchUsers', { query: state.searchQuery, page: 1 })
|
||||
},
|
||||
async ToggleRight({ commit, getters }, { user, right }) {
|
||||
user.roles[right]
|
||||
? await deleteRight(user.nickname, right, getters.authHost, getters.token)
|
||||
: await addRight(user.nickname, right, getters.authHost, getters.token)
|
||||
|
||||
const updatedUser = { ...user, roles: { ...user.roles, [right]: !user.roles[right] }}
|
||||
commit('SWAP_USER', updatedUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,20 @@ const service = axios.create({
|
|||
service.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
let errorMessage
|
||||
console.log(`Error ${error}`)
|
||||
console.log(error.response.data)
|
||||
|
||||
// If there's an "error" property in the json, use it
|
||||
const edata = error.response.data.error ? error.response.data.error : error.response.data
|
||||
if (error.response) {
|
||||
const edata = error.response.data.error ? error.response.data.error : error.response.data
|
||||
errorMessage = !error.response.headers['content-type'].includes('application/json')
|
||||
? `${error.message}`
|
||||
: `${error.message} - ${edata}`
|
||||
} else {
|
||||
errorMessage = error
|
||||
}
|
||||
|
||||
Message({
|
||||
message: `${error.message} - ${edata}`,
|
||||
message: errorMessage,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
|
|
|
@ -1,218 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<h2>{{ name }}</h2>
|
||||
|
||||
<prop-editing-row name="Share pack">
|
||||
<el-switch v-model="share" :disabled="!isLocal" />
|
||||
</prop-editing-row>
|
||||
<prop-editing-row name="Homepage">
|
||||
<el-input v-if="isLocal" v-model="homepage" />
|
||||
<el-input v-else :value="homepage" />
|
||||
</prop-editing-row>
|
||||
<prop-editing-row name="Description">
|
||||
<el-input v-if="isLocal" :rows="2" v-model="description" type="textarea" />
|
||||
<el-input v-else :rows="2" :value="description" type="textarea" />
|
||||
</prop-editing-row>
|
||||
<prop-editing-row name="License">
|
||||
<el-input v-if="isLocal" v-model="license" />
|
||||
<el-input v-else :value="license" />
|
||||
</prop-editing-row>
|
||||
<prop-editing-row name="Fallback source">
|
||||
<el-input v-if="isLocal" v-model="fallbackSrc" />
|
||||
<el-input v-else :value="fallbackSrc" />
|
||||
</prop-editing-row>
|
||||
|
||||
<prop-editing-row v-if="fallbackSrc && fallbackSrc.trim() !== ''" name="Fallback source SHA">
|
||||
{{ pack.pack["fallback-src-sha256"] }}
|
||||
</prop-editing-row>
|
||||
|
||||
<el-button v-if="isLocal" type="success" @click="savePackMetadata">Save pack metadata</el-button>
|
||||
|
||||
<el-collapse v-model="shownPackEmoji" class="contents-collapse">
|
||||
<el-collapse-item :name="name" title="Show pack contents">
|
||||
<new-emoji-uploader v-if="isLocal" :pack-name="name" class="new-emoji-uploader" />
|
||||
|
||||
<h4>Manage existing emoji</h4>
|
||||
|
||||
<single-emoji-editor
|
||||
v-for="(file, ename) in pack.files"
|
||||
:key="ename"
|
||||
:host="host"
|
||||
:pack-name="name"
|
||||
:name="ename"
|
||||
:file="file"
|
||||
:is-local="isLocal" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<div v-if="!isLocal" class="shared-pack-dl-box">
|
||||
<div>
|
||||
This will download the "{{ name }}" pack to the current instance under the name
|
||||
"{{ downloadSharedAs.trim() === '' ? name : downloadSharedAs }}" (can be changed below).
|
||||
It will then be usable and shareable from the current instance.
|
||||
</div>
|
||||
|
||||
<el-button type="primary" @click="downloadFromInstance">
|
||||
Download shared pack to current instance
|
||||
</el-button>
|
||||
|
||||
<el-input v-model="downloadSharedAs" class="dl-as-input" placeholder="Download as (optional)" />
|
||||
</div>
|
||||
|
||||
<el-link
|
||||
v-if="pack.pack['can-download']"
|
||||
:href="`//${host}/api/pleroma/emoji/packs/${name}/download_shared`"
|
||||
type="primary"
|
||||
target="_blank">
|
||||
Download pack archive
|
||||
</el-link>
|
||||
|
||||
<div v-if="isLocal" class="pack-actions">
|
||||
<el-button type="danger" @click="deletePack">
|
||||
Delete the local pack
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.shared-pack-dl-box {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.dl-as-input {
|
||||
margin: 1em;
|
||||
max-width: 30%;
|
||||
}
|
||||
|
||||
.contents-collapse {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.pack-actions {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.new-emoji-uploader {
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import PropEditingRow from './PropertyEditingRow.vue'
|
||||
import SingleEmojiEditor from './SingleEmojiEditor.vue'
|
||||
import NewEmojiUploader from './NewEmojiUploader.vue'
|
||||
|
||||
export default {
|
||||
|
||||
components: { PropEditingRow, SingleEmojiEditor, NewEmojiUploader },
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
pack: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
host: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
isLocal: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
shownPackEmoji: [],
|
||||
downloadSharedAs: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
share: {
|
||||
get() { return this.pack.pack['share-files'] },
|
||||
set(value) {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'share-files', value }
|
||||
)
|
||||
}
|
||||
},
|
||||
homepage: {
|
||||
get() { return this.pack.pack['homepage'] },
|
||||
set(value) {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'homepage', value }
|
||||
)
|
||||
}
|
||||
},
|
||||
description: {
|
||||
get() { return this.pack.pack['description'] },
|
||||
set(value) {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'description', value }
|
||||
)
|
||||
}
|
||||
},
|
||||
license: {
|
||||
get() { return this.pack.pack['license'] },
|
||||
set(value) {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'license', value }
|
||||
)
|
||||
}
|
||||
},
|
||||
fallbackSrc: {
|
||||
get() { return this.pack.pack['fallback-src'] },
|
||||
set(value) {
|
||||
if (value.trim() !== '') {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'fallback-src', value }
|
||||
)
|
||||
} else {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'fallback-src', value: null }
|
||||
)
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'fallback-src-sha256', value: null }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
downloadFromInstance() {
|
||||
this.$store.dispatch(
|
||||
'DownloadFrom',
|
||||
{ instanceAddress: this.host, packName: this.name, as: this.downloadSharedAs }
|
||||
).then(() => this.$store.dispatch('ReloadEmoji'))
|
||||
.then(() => this.$store.dispatch('SetLocalEmojiPacks'))
|
||||
},
|
||||
|
||||
deletePack() {
|
||||
this.$confirm('This will delete the pack, are you sure?', 'Warning', {
|
||||
confirmButtonText: 'Yes, delete the pack',
|
||||
cancelButtonText: 'No, leave it be',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$store.dispatch('DeletePack', { name: this.name })
|
||||
.then(() => this.$store.dispatch('ReloadEmoji'))
|
||||
.then(() => this.$store.dispatch('SetLocalEmojiPacks'))
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
savePackMetadata() {
|
||||
this.$store.dispatch('SavePackMetadata', { packName: this.name })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,93 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<h4>Add new emoji to the pack</h4>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4" class="new-emoji-col">
|
||||
<el-input v-model="shortcode" placeholder="Shortcode" />
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<div>
|
||||
<h5>Upload a file</h5>
|
||||
</div>
|
||||
File name
|
||||
<el-input v-model="customFileName" size="mini" placeholder="Custom file name (optional)"/>
|
||||
<input ref="fileUpload" type="file" accept="image/*" >
|
||||
|
||||
<div class="or">
|
||||
or
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5>Enter a URL</h5>
|
||||
</div>
|
||||
<el-input v-model="imageUploadURL" placeholder="Image URL" />
|
||||
|
||||
<small>
|
||||
(If both are filled, the file is used)
|
||||
</small>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="4" class="new-emoji-col">
|
||||
<el-button :disabled="shortcode.trim() == ''" @click="upload">Upload</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.new-emoji-col {
|
||||
margin-top: 8em;
|
||||
}
|
||||
|
||||
.or {
|
||||
margin: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
packName: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
shortcode: '',
|
||||
imageUploadURL: '',
|
||||
customFileName: ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
upload() {
|
||||
let file = null
|
||||
|
||||
if (this.$refs.fileUpload.files.length > 0) {
|
||||
file = this.$refs.fileUpload.files[0]
|
||||
} else if (this.imageUploadURL.trim() !== '') {
|
||||
file = this.imageUploadURL
|
||||
}
|
||||
|
||||
if (file !== null) {
|
||||
this.$store.dispatch('UpdateAndSavePackFile', {
|
||||
action: 'add',
|
||||
packName: this.packName,
|
||||
shortcode: this.shortcode,
|
||||
file: file,
|
||||
fileName: this.customFileName
|
||||
}).then(() => {
|
||||
this.shortcode = ''
|
||||
this.imageUploadURL = ''
|
||||
|
||||
this.$store.dispatch('ReloadEmoji')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,27 +0,0 @@
|
|||
<template>
|
||||
<el-row :gutter="20" class="prop-row">
|
||||
<el-col :span="4">
|
||||
<b>{{ name }}</b>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<slot/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.prop-row {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,151 +0,0 @@
|
|||
<template>
|
||||
<el-container class="emoji-packs-container">
|
||||
<el-header>
|
||||
<h1>
|
||||
Emoji packs
|
||||
</h1>
|
||||
</el-header>
|
||||
|
||||
<el-row class="local-packs-actions">
|
||||
<el-button type="primary" @click="reloadEmoji">
|
||||
Reload emoji
|
||||
</el-button>
|
||||
|
||||
<el-tooltip effects="dark" content="Importing from the filesystem will scan the directories and import those without pack.json but with emoji.txt or without neither" placement="bottom">
|
||||
<el-button type="success" @click="importFromFS">
|
||||
Import packs from the server filesystem
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-row>
|
||||
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="Local packs" name="local">
|
||||
<div>
|
||||
Local packs can be viewed and downloaded for backup here.
|
||||
</div>
|
||||
|
||||
<div class="local-packs-actions">
|
||||
<el-popover
|
||||
v-model="createNewPackVisible"
|
||||
placement="bottom"
|
||||
trigger="click">
|
||||
|
||||
<el-input v-model="newPackName" placeholder="Name" />
|
||||
<el-button
|
||||
:disabled="newPackName.trim() === ''"
|
||||
class="create-pack-button"
|
||||
type="success"
|
||||
@click="createLocalPack" >
|
||||
Create
|
||||
</el-button>
|
||||
|
||||
<el-button slot="reference" type="success">
|
||||
Create a new local pack
|
||||
</el-button>
|
||||
</el-popover>
|
||||
|
||||
<el-button type="primary" @click="refreshLocalPacks">
|
||||
Refresh local packs
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div v-for="(pack, name) in $store.state.emoji_packs.localPacks" :key="name">
|
||||
<emoji-pack :name="name" :pack="pack" :host="$store.getters.authHost" :is-local="true" />
|
||||
<el-divider />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="Remote packs" name="remote">
|
||||
<el-input
|
||||
v-model="remoteInstanceAddress"
|
||||
class="remote-instance-input"
|
||||
placeholder="Remote instance address" />
|
||||
<el-button type="primary" @click="refreshRemotePacks">
|
||||
Refresh remote packs
|
||||
</el-button>
|
||||
|
||||
<div v-for="(pack, name) in $store.state.emoji_packs.remotePacks" :key="name">
|
||||
<emoji-pack :name="name" :pack="pack" :host="remoteInstanceAddress" :is-local="false" />
|
||||
<el-divider />
|
||||
</div>
|
||||
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.emoji-packs-container {
|
||||
margin: 22px 0 0 15px;
|
||||
}
|
||||
|
||||
.local-packs-actions {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.remote-instance-input {
|
||||
max-width: 10%;
|
||||
}
|
||||
|
||||
.create-pack-button {
|
||||
margin-top: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import EmojiPack from './components/EmojiPack'
|
||||
|
||||
export default {
|
||||
components: { EmojiPack },
|
||||
|
||||
data() {
|
||||
return {
|
||||
activeName: 'local',
|
||||
remoteInstanceAddress: '',
|
||||
downloadFromState: null,
|
||||
|
||||
newPackName: '',
|
||||
createNewPackVisible: false
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.refreshLocalPacks()
|
||||
},
|
||||
|
||||
methods: {
|
||||
createLocalPack() {
|
||||
this.createNewPackVisible = false
|
||||
|
||||
this.$store.dispatch('CreatePack', { name: this.newPackName })
|
||||
.then(() => {
|
||||
this.newPackName = ''
|
||||
|
||||
this.$store.dispatch('SetLocalEmojiPacks')
|
||||
this.$store.dispatch('ReloadEmoji')
|
||||
})
|
||||
},
|
||||
|
||||
refreshLocalPacks() {
|
||||
this.$store.dispatch('SetLocalEmojiPacks')
|
||||
},
|
||||
|
||||
refreshRemotePacks() {
|
||||
this.$store.dispatch('SetRemoteEmojiPacks', { remoteInstance: this.remoteInstanceAddress })
|
||||
},
|
||||
|
||||
reloadEmoji() {
|
||||
this.$store.dispatch('ReloadEmoji')
|
||||
},
|
||||
|
||||
importFromFS() {
|
||||
this.$store.dispatch('ImportFromFS')
|
||||
.then(() => {
|
||||
this.$store.dispatch('SetLocalEmojiPacks')
|
||||
this.$store.dispatch('ReloadEmoji')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
257
src/views/emojiPacks/components/EmojiPack.vue
Normal file
257
src/views/emojiPacks/components/EmojiPack.vue
Normal file
|
@ -0,0 +1,257 @@
|
|||
<template>
|
||||
<el-collapse-item :title="name" :name="name" class="has-background">
|
||||
<el-form v-if="isLocal" label-width="120px" label-position="left" size="small" class="emoji-pack-metadata">
|
||||
<el-form-item :label="$t('settings.sharePack')">
|
||||
<el-switch v-model="share" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('settings.homepage')">
|
||||
<el-input v-model="homepage" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('settings.description')">
|
||||
<el-input v-model="description" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('settings.license')">
|
||||
<el-input v-model="license" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('settings.fallbackSrc')">
|
||||
<el-input v-model="fallbackSrc" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="fallbackSrc && fallbackSrc.trim() !== ''"
|
||||
:label="$t('settings.fallbackSrcSha')">
|
||||
{{ pack.pack["fallback-src-sha256"] }}
|
||||
</el-form-item>
|
||||
<el-form-item class="save-pack-button">
|
||||
<el-button type="primary" @click="savePackMetadata">{{ $t('settings.savePackMetadata') }}</el-button>
|
||||
<el-button @click="deletePack">{{ $t('settings.deletePack') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-link
|
||||
v-if="pack.pack['can-download']"
|
||||
:href="`//${host}/api/pleroma/emoji/packs/${name}/download_shared`"
|
||||
:underline="false"
|
||||
type="primary"
|
||||
target="_blank">
|
||||
<el-button class="download-archive">{{ $t('settings.downloadPackArchive') }}</el-button>
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form v-if="!isLocal" label-width="120px" label-position="left" size="small" class="emoji-pack-metadata">
|
||||
<el-form-item :label="$t('settings.sharePack')">
|
||||
<el-switch v-model="share" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="homepage" :label="$t('settings.homepage')">
|
||||
<span>{{ homepage }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="description" :label="$t('settings.description')">
|
||||
<span>{{ description }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="license" :label="$t('settings.license')">
|
||||
<span>{{ license }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="fallbackSrc" :label="$t('settings.fallbackSrc')">
|
||||
<span>{{ fallbackSrc }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="fallbackSrc && fallbackSrc.trim() !== ''"
|
||||
:label="$t('settings.fallbackSrcSha')">
|
||||
{{ pack.pack["fallback-src-sha256"] }}
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-link
|
||||
v-if="pack.pack['can-download']"
|
||||
:href="`//${host}/api/pleroma/emoji/packs/${name}/download_shared`"
|
||||
:underline="false"
|
||||
type="primary"
|
||||
target="_blank">
|
||||
<el-button class="download-archive">{{ $t('settings.downloadPackArchive') }}</el-button>
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-collapse v-model="showPackContent" class="contents-collapse">
|
||||
<el-collapse-item v-if="isLocal" :title="$t('settings.addNewEmoji')" name="addEmoji" class="no-background">
|
||||
<new-emoji-uploader :pack-name="name"/>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item v-if="Object.keys(pack.files).length > 0" :title="$t('settings.manageEmoji')" name="manageEmoji" class="no-background">
|
||||
<single-emoji-editor
|
||||
v-for="(file, ename) in pack.files"
|
||||
:key="ename"
|
||||
:host="host"
|
||||
:pack-name="name"
|
||||
:name="ename"
|
||||
:file="file"
|
||||
:is-local="isLocal" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item v-if="!isLocal" :title="$t('settings.downloadPack')" name="downloadPack" class="no-background">
|
||||
<p>
|
||||
{{ $t('settings.thisWillDownload') }} "{{ name }}" {{ $t('settings.downloadToCurrentInstance') }}
|
||||
"{{ downloadSharedAs.trim() === '' ? name : downloadSharedAs }}" ({{ $t('settings.canBeChanged') }}).
|
||||
{{ $t('settings.willBeUsable') }}.
|
||||
</p>
|
||||
<div class="download-shared-pack">
|
||||
<el-input v-model="downloadSharedAs" :placeholder="$t('settings.downloadAsOptional')"/>
|
||||
<el-button type="primary" class="download-shared-pack-button" @click="downloadFromInstance">
|
||||
{{ $t('settings.downloadSharedPack') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-collapse-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SingleEmojiEditor from './SingleEmojiEditor.vue'
|
||||
import NewEmojiUploader from './NewEmojiUploader.vue'
|
||||
|
||||
export default {
|
||||
|
||||
components: { SingleEmojiEditor, NewEmojiUploader },
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
pack: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
host: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
isLocal: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showPackContent: [],
|
||||
downloadSharedAs: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
share: {
|
||||
get() { return this.pack.pack['share-files'] },
|
||||
set(value) {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'share-files', value }
|
||||
)
|
||||
}
|
||||
},
|
||||
homepage: {
|
||||
get() { return this.pack.pack['homepage'] },
|
||||
set(value) {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'homepage', value }
|
||||
)
|
||||
}
|
||||
},
|
||||
description: {
|
||||
get() { return this.pack.pack['description'] },
|
||||
set(value) {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'description', value }
|
||||
)
|
||||
}
|
||||
},
|
||||
license: {
|
||||
get() { return this.pack.pack['license'] },
|
||||
set(value) {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'license', value }
|
||||
)
|
||||
}
|
||||
},
|
||||
fallbackSrc: {
|
||||
get() { return this.pack.pack['fallback-src'] },
|
||||
set(value) {
|
||||
if (value.trim() !== '') {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'fallback-src', value }
|
||||
)
|
||||
} else {
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'fallback-src', value: null }
|
||||
)
|
||||
this.$store.dispatch(
|
||||
'UpdateLocalPackVal',
|
||||
{ name: this.name, key: 'fallback-src-sha256', value: null }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
downloadFromInstance() {
|
||||
this.$store.dispatch(
|
||||
'DownloadFrom',
|
||||
{ instanceAddress: this.host, packName: this.name, as: this.downloadSharedAs }
|
||||
).then(() => this.$store.dispatch('ReloadEmoji'))
|
||||
.then(() => this.$store.dispatch('SetLocalEmojiPacks'))
|
||||
},
|
||||
|
||||
deletePack() {
|
||||
this.$confirm('This will delete the pack, are you sure?', 'Warning', {
|
||||
confirmButtonText: 'Yes, delete the pack',
|
||||
cancelButtonText: 'No, leave it be',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$store.dispatch('DeletePack', { name: this.name })
|
||||
.then(() => this.$store.dispatch('ReloadEmoji'))
|
||||
.then(() => this.$store.dispatch('SetLocalEmojiPacks'))
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
savePackMetadata() {
|
||||
this.$store.dispatch('SavePackMetadata', { packName: this.name })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel='stylesheet/scss' lang='scss'>
|
||||
.download-archive {
|
||||
width: 250px
|
||||
}
|
||||
.download-shared-pack {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.download-shared-pack-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.el-collapse-item__content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.el-collapse-item__header {
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #606266;
|
||||
}
|
||||
.emoji-pack-card {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.emoji-pack-metadata {
|
||||
.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.has-background .el-collapse-item__header {
|
||||
background: #f6f6f6;
|
||||
}
|
||||
.no-background .el-collapse-item__header {
|
||||
background: white;
|
||||
}
|
||||
.save-pack-button {
|
||||
margin-bottom: 5px
|
||||
}
|
||||
</style>
|
90
src/views/emojiPacks/components/NewEmojiUploader.vue
Normal file
90
src/views/emojiPacks/components/NewEmojiUploader.vue
Normal file
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<el-form label-width="130px" label-position="left" size="small">
|
||||
<el-form-item :label="$t('settings.shortcode')">
|
||||
<el-input v-model="shortcode" :placeholder="$t('settings.required')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('settings.customFilename')">
|
||||
<el-input v-model="customFileName" :placeholder="$t('settings.optional')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('settings.uploadFile')">
|
||||
<div class="upload-file-url">
|
||||
<el-input v-model="imageUploadURL" :placeholder="$t('settings.url')"/>
|
||||
<el-button :disabled="shortcodePresent" type="primary" class="upload-button" @click="uploadEmoji">{{ $t('settings.upload') }}</el-button>
|
||||
</div>
|
||||
<div class="upload-container">
|
||||
<p class="text">or</p>
|
||||
<el-upload
|
||||
:http-request="uploadEmoji"
|
||||
:multiple="false"
|
||||
:show-file-list="false"
|
||||
action="add">
|
||||
<el-button :disabled="shortcodePresent" type="primary">{{ $t('settings.clickToUpload') }}</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.add-new-emoji {
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #606266;
|
||||
}
|
||||
.text {
|
||||
line-height: 20px;
|
||||
margin-right: 15px
|
||||
}
|
||||
.upload-container {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
.upload-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.upload-file-url {
|
||||
display: flex;
|
||||
justify-content: space-between
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
packName: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
shortcode: '',
|
||||
imageUploadURL: '',
|
||||
customFileName: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
shortcodePresent() {
|
||||
return this.shortcode.trim() === ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
uploadEmoji({ file }) {
|
||||
this.$store.dispatch('UpdateAndSavePackFile', {
|
||||
action: 'add',
|
||||
packName: this.packName,
|
||||
shortcode: this.shortcode,
|
||||
file: file || this.imageUploadURL,
|
||||
fileName: this.customFileName
|
||||
}).then(() => {
|
||||
this.shortcode = ''
|
||||
this.imageUploadURL = ''
|
||||
this.customFileName = ''
|
||||
|
||||
this.$store.dispatch('ReloadEmoji')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,71 +1,50 @@
|
|||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<el-input v-if="isLocal" v-model="modifyingName" placeholder="Name/Shortcode" />
|
||||
<el-input v-else :value="modifyingName" placeholder="Name/Shortcode" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-input v-if="isLocal" v-model="modifyingFile" placeholder="File"/>
|
||||
<el-input v-else :value="modifyingFile" placeholder="File"/>
|
||||
</el-col>
|
||||
<div>
|
||||
<div v-if="isLocal" class="emoji-container">
|
||||
<img
|
||||
:src="addressOfEmojiInPack(host, packName, file)"
|
||||
class="emoji-preview-img">
|
||||
<el-input v-model="emojiName" :placeholder="$t('settings.shortcode')" class="emoji-info"/>
|
||||
<el-input v-model="emojiFile" :placeholder="$t('settings.file')" class="emoji-info"/>
|
||||
<div class="emoji-buttons">
|
||||
<el-button type="primary" class="emoji-button" @click="update">{{ $t('settings.update') }}</el-button>
|
||||
<el-button class="emoji-button" @click="remove">{{ $t('settings.remove') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-col v-if="isLocal" :span="2">
|
||||
<el-button type="primary" @click="update">Update</el-button>
|
||||
</el-col>
|
||||
<el-col v-if="isLocal" :span="2">
|
||||
<el-button type="danger" @click="remove">Remove</el-button>
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="!isLocal" :span="4">
|
||||
<el-popover v-model="copyToLocalVisible" placement="bottom">
|
||||
<p>Select the local pack to copy to</p>
|
||||
<el-select v-model="copyToLocalPackName" placeholder="Local pack">
|
||||
<div v-if="!isLocal" class="emoji-container">
|
||||
<img
|
||||
:src="addressOfEmojiInPack(host, packName, file)"
|
||||
class="emoji-preview-img">
|
||||
<el-input :value="emojiName" :placeholder="$t('settings.shortcode')" class="emoji-info"/>
|
||||
<el-input :value="emojiFile" :placeholder="$t('settings.file')" class="emoji-info"/>
|
||||
<el-popover v-model="copyPopoverVisible" placement="left-start" popper-class="copy-popover">
|
||||
<p>{{ $t('settings.selectLocalPack') }}</p>
|
||||
<el-select v-model="copyToLocalPackName" :placeholder="$t('settings.localPack')">
|
||||
<el-option
|
||||
v-for="(_pack, name) in $store.state.emoji_packs.localPacks"
|
||||
v-for="(_pack, name) in localPacks"
|
||||
:key="name"
|
||||
:label="name"
|
||||
:value="name" />
|
||||
</el-select>
|
||||
|
||||
<p>Specify a custom shortcode (leave empty to use the same shortcode)</p>
|
||||
<el-input v-model="copyToShortcode" placeholder="Shortcode (optional)" />
|
||||
|
||||
<p>Specify a custom filename (leavy empty to use the same filename)</p>
|
||||
<el-input v-model="copyToFilename" placeholder="Filename (optional)" />
|
||||
|
||||
<p>{{ $t('settings.specifyShortcode') }}</p>
|
||||
<el-input v-model="copyToShortcode" :placeholder="$t('settings.leaveEmptyShortcode')"/>
|
||||
<p>{{ $t('settings.specifyFilename') }}</p>
|
||||
<el-input v-model="copyToFilename" :placeholder="$t('settings.leaveEmptyFilename')"/>
|
||||
<el-button
|
||||
:disabled="!copyToLocalPackName"
|
||||
type="success"
|
||||
type="primary"
|
||||
class="copy-to-local-button"
|
||||
@click="copyToLocal">Copy</el-button>
|
||||
|
||||
<el-button slot="reference" type="primary">Copy to local pack...</el-button>
|
||||
@click="copyToLocal">{{ $t('settings.copy') }}</el-button>
|
||||
<el-button slot="reference" type="primary" class="emoji-button">{{ $t('settings.copyToLocalPack') }}</el-button>
|
||||
</el-popover>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="2">
|
||||
<img
|
||||
:src="addressOfEmojiInPack(host, packName, file)"
|
||||
class="emoji-preview-img">
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.emoji-preview-img {
|
||||
max-width: 5em;
|
||||
}
|
||||
|
||||
.copy-to-local-button {
|
||||
margin-top: 2em;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import { addressOfEmojiInPack } from '@/api/emoji_packs'
|
||||
|
||||
import { addressOfEmojiInPack } from '@/api/emojiPacks'
|
||||
export default {
|
||||
props: {
|
||||
host: {
|
||||
|
@ -89,33 +68,33 @@ export default {
|
|||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
newName: null,
|
||||
newFile: null,
|
||||
|
||||
copyToLocalPackName: null,
|
||||
copyToLocalVisible: false,
|
||||
copyPopoverVisible: false,
|
||||
copyToShortcode: '',
|
||||
copyToFilename: ''
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
modifyingName: {
|
||||
emojiName: {
|
||||
get() {
|
||||
// Return a modified name if it was actually modified, otherwise return the old name
|
||||
// Return a modified name if it was modified, otherwise return the old name
|
||||
return this.newName !== null ? this.newName : this.name
|
||||
},
|
||||
set(val) { this.newName = val }
|
||||
},
|
||||
modifyingFile: {
|
||||
emojiFile: {
|
||||
get() {
|
||||
// Return a modified name if it was actually modified, otherwise return the old name
|
||||
// Return a modified name if it was modified, otherwise return the old name
|
||||
return this.newFile !== null ? this.newFile : this.file
|
||||
},
|
||||
set(val) { this.newFile = val }
|
||||
},
|
||||
localPacks() {
|
||||
return this.$store.state.emojiPacks.localPacks
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -124,8 +103,8 @@ export default {
|
|||
action: 'update',
|
||||
packName: this.packName,
|
||||
oldName: this.name,
|
||||
newName: this.modifyingName,
|
||||
newFilename: this.modifyingFile
|
||||
newName: this.emojiName,
|
||||
newFilename: this.emojiFile
|
||||
}).then(() => {
|
||||
this.newName = null
|
||||
this.newFile = null
|
||||
|
@ -151,7 +130,6 @@ export default {
|
|||
})
|
||||
})
|
||||
},
|
||||
|
||||
copyToLocal() {
|
||||
this.$store.dispatch('UpdateAndSavePackFile', {
|
||||
action: 'add',
|
||||
|
@ -168,8 +146,35 @@ export default {
|
|||
this.$store.dispatch('ReloadEmoji')
|
||||
})
|
||||
},
|
||||
|
||||
addressOfEmojiInPack
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.copy-popover {
|
||||
width: 330px
|
||||
}
|
||||
.emoji-button {
|
||||
margin-left: 10px
|
||||
}
|
||||
.emoji-buttons {
|
||||
min-width: 210px
|
||||
}
|
||||
.emoji-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.emoji-preview-img {
|
||||
max-width: 5em;
|
||||
}
|
||||
.emoji-info {
|
||||
margin-left: 10px
|
||||
}
|
||||
.copy-to-local-button {
|
||||
margin-top: 12px;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
132
src/views/emojiPacks/index.vue
Normal file
132
src/views/emojiPacks/index.vue
Normal file
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="button-container">
|
||||
<el-button type="primary" @click="reloadEmoji">{{ $t('settings.reloadEmoji') }}</el-button>
|
||||
<el-tooltip :content="$t('settings.importEmojiTooltip')" effects="dark" placement="bottom">
|
||||
<el-button type="primary" @click="importFromFS">
|
||||
{{ $t('settings.importPacks') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="line"/>
|
||||
<el-form :label-width="labelWidth">
|
||||
<el-form-item :label="$t('settings.localPacks')">
|
||||
<el-button type="primary" @click="refreshLocalPacks">{{ $t('settings.refreshLocalPacks') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('settings.createLocalPack')">
|
||||
<div class="create-pack">
|
||||
<el-input v-model="newPackName" :placeholder="$t('users.name')" />
|
||||
<el-button
|
||||
:disabled="newPackName.trim() === ''"
|
||||
class="create-pack-button"
|
||||
@click="createLocalPack">
|
||||
{{ $t('users.create') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="Object.keys(localPacks).length > 0" :label="$t('settings.packs')">
|
||||
<el-collapse v-for="(pack, name) in localPacks" :key="name" v-model="activeLocalPack">
|
||||
<emoji-pack :name="name" :pack="pack" :host="$store.getters.authHost" :is-local="true" />
|
||||
</el-collapse>
|
||||
</el-form-item>
|
||||
<div class="line"/>
|
||||
<el-form-item :label="$t('settings.remotePacks')">
|
||||
<div class="create-pack">
|
||||
<el-input
|
||||
v-model="remoteInstanceAddress"
|
||||
:placeholder="$t('settings.remoteInstanceAddress')" />
|
||||
<el-button
|
||||
:disabled="remoteInstanceAddress.trim() === ''"
|
||||
class="create-pack-button"
|
||||
@click="refreshRemotePacks">
|
||||
{{ $t('settings.refreshRemote') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="Object.keys(remotePacks).length > 0" :label="$t('settings.packs')">
|
||||
<el-collapse v-for="(pack, name) in remotePacks" :key="name" v-model="activeRemotePack">
|
||||
<emoji-pack :name="name" :pack="pack" :host="$store.getters.authHost" :is-local="false" />
|
||||
</el-collapse>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmojiPack from './components/EmojiPack'
|
||||
|
||||
export default {
|
||||
components: { EmojiPack },
|
||||
data() {
|
||||
return {
|
||||
remoteInstanceAddress: '',
|
||||
newPackName: '',
|
||||
activeLocalPack: [],
|
||||
activeRemotePack: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return this.$store.state.app.device === 'mobile'
|
||||
},
|
||||
labelWidth() {
|
||||
return this.isMobile ? '100px' : '210px'
|
||||
},
|
||||
localPacks() {
|
||||
return this.$store.state.emojiPacks.localPacks
|
||||
},
|
||||
remotePacks() {
|
||||
return this.$store.state.emojiPacks.remotePacks
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.refreshLocalPacks()
|
||||
},
|
||||
methods: {
|
||||
createLocalPack() {
|
||||
this.$store.dispatch('CreatePack', { name: this.newPackName })
|
||||
.then(() => {
|
||||
this.newPackName = ''
|
||||
|
||||
this.$store.dispatch('SetLocalEmojiPacks')
|
||||
this.$store.dispatch('ReloadEmoji')
|
||||
})
|
||||
},
|
||||
refreshLocalPacks() {
|
||||
this.$store.dispatch('SetLocalEmojiPacks')
|
||||
},
|
||||
refreshRemotePacks() {
|
||||
this.$store.dispatch('SetRemoteEmojiPacks', { remoteInstance: this.remoteInstanceAddress })
|
||||
},
|
||||
reloadEmoji() {
|
||||
this.$store.dispatch('ReloadEmoji')
|
||||
},
|
||||
importFromFS() {
|
||||
this.$store.dispatch('ImportFromFS')
|
||||
.then(() => {
|
||||
this.$store.dispatch('SetLocalEmojiPacks')
|
||||
this.$store.dispatch('ReloadEmoji')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel='stylesheet/scss' lang='scss'>
|
||||
.button-container {
|
||||
margin: 0 0 22px 20px;
|
||||
}
|
||||
.create-pack {
|
||||
display: flex;
|
||||
justify-content: space-between
|
||||
}
|
||||
.create-pack-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.line {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
border: 1px solid #eee;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
</style>
|
|
@ -21,7 +21,7 @@
|
|||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-if="showDeactivatedButton(report.account)"
|
||||
@click.native="handleDeactivation(report.account)">
|
||||
@click.native="toggleActivation(report.account)">
|
||||
{{ report.account.deactivated ? $t('users.activateAccount') : $t('users.deactivateAccount') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
|
@ -139,17 +139,19 @@ export default {
|
|||
return 'primary'
|
||||
}
|
||||
},
|
||||
handleDeletion(user) {
|
||||
this.$store.dispatch('DeleteUsers', [user])
|
||||
},
|
||||
parseTimestamp(timestamp) {
|
||||
return moment(timestamp).format('L HH:mm')
|
||||
},
|
||||
showDeactivatedButton(id) {
|
||||
return this.$store.state.user.id !== id
|
||||
},
|
||||
handleDeactivation({ nickname }) {
|
||||
this.$store.dispatch('ToggleUserActivation', nickname)
|
||||
},
|
||||
handleDeletion(user) {
|
||||
this.$store.dispatch('DeleteUser', user)
|
||||
toggleActivation(user) {
|
||||
user.deactivated
|
||||
? this.$store.dispatch('ActivateUsers', [user])
|
||||
: this.$store.dispatch('DeactivateUsers', [user])
|
||||
},
|
||||
toggleTag(user, tag) {
|
||||
user.tags.includes(tag)
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
<el-tab-pane :label="$t('settings.endpoint')">
|
||||
<endpoint/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('settings.emojiPacks')">
|
||||
<emoji-packs/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('settings.frontend')">
|
||||
<frontend/>
|
||||
</el-tab-pane>
|
||||
|
@ -74,9 +77,10 @@
|
|||
|
||||
<script>
|
||||
import { ActivityPub, Authentication, AutoLinker, Captcha, Database, Endpoint, Esshd, Frontend, Gopher, Http, Instance, JobQueue, Logger, Mailer, MediaProxy, Metadata, Mrf, Other, RateLimiters, Relays, Upload, WebPush } from './components'
|
||||
import EmojiPacks from '../emojiPacks/index'
|
||||
|
||||
export default {
|
||||
components: { ActivityPub, Authentication, AutoLinker, Captcha, Database, Endpoint, Esshd, Frontend, Gopher, Http, Instance, JobQueue, Logger, Mailer, MediaProxy, Metadata, Mrf, Other, RateLimiters, Relays, Upload, WebPush },
|
||||
components: { ActivityPub, Authentication, AutoLinker, Captcha, Database, Endpoint, EmojiPacks, Esshd, Frontend, Gopher, Http, Instance, JobQueue, Logger, Mailer, MediaProxy, Metadata, Mrf, Other, RateLimiters, Relays, Upload, WebPush },
|
||||
computed: {
|
||||
isMobile() {
|
||||
return this.$store.state.app.device === 'mobile'
|
||||
|
|
|
@ -150,87 +150,69 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
mappers() {
|
||||
const applyActionToAllUsers = (filteredUsers, fn) => Promise.all(filteredUsers.map(fn))
|
||||
.then(() => {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: this.$t('users.completed')
|
||||
})
|
||||
this.$emit('apply-action')
|
||||
}).catch((err) => {
|
||||
const applyAction = async(users, dispatchAction) => {
|
||||
try {
|
||||
await dispatchAction(users)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: this.$t('users.completed')
|
||||
})
|
||||
this.$emit('apply-action')
|
||||
}
|
||||
return {
|
||||
grantRight: (right) => () => {
|
||||
const filterUsersFn = user => user.local && !user.roles[right] && this.$store.state.user.id !== user.id
|
||||
const toggleRightFn = async(user) => await this.$store.dispatch('ToggleRight', { user, right })
|
||||
const addRightFn = async(users) => await this.$store.dispatch('AddRight', { users, right })
|
||||
const filtered = this.selectedUsers.filter(filterUsersFn)
|
||||
|
||||
applyActionToAllUsers(filtered, toggleRightFn)
|
||||
applyAction(filtered, addRightFn)
|
||||
},
|
||||
revokeRight: (right) => () => {
|
||||
const filterUsersFn = user => user.local && user.roles[right] && this.$store.state.user.id !== user.id
|
||||
const toggleRightFn = async(user) => await this.$store.dispatch('ToggleRight', { user, right })
|
||||
const deleteRightFn = async(users) => await this.$store.dispatch('DeleteRight', { users, right })
|
||||
const filtered = this.selectedUsers.filter(filterUsersFn)
|
||||
|
||||
applyActionToAllUsers(filtered, toggleRightFn)
|
||||
applyAction(filtered, deleteRightFn)
|
||||
},
|
||||
activate: () => {
|
||||
const filtered = this.selectedUsers.filter(user => user.deactivated && this.$store.state.user.id !== user.id)
|
||||
const toggleActivationFn = async(user) => await this.$store.dispatch('ToggleUserActivation', user.nickname)
|
||||
const activateUsersFn = async(users) => await this.$store.dispatch('ActivateUsers', users)
|
||||
|
||||
applyActionToAllUsers(filtered, toggleActivationFn)
|
||||
applyAction(filtered, activateUsersFn)
|
||||
},
|
||||
deactivate: () => {
|
||||
const filtered = this.selectedUsers.filter(user => !user.deactivated && this.$store.state.user.id !== user.id)
|
||||
const toggleActivationFn = async(user) => await this.$store.dispatch('ToggleUserActivation', user.nickname)
|
||||
const deactivateUsersFn = async(users) => await this.$store.dispatch('DeactivateUsers', users)
|
||||
|
||||
applyActionToAllUsers(filtered, toggleActivationFn)
|
||||
applyAction(filtered, deactivateUsersFn)
|
||||
},
|
||||
remove: () => {
|
||||
const filtered = this.selectedUsers.filter(user => this.$store.state.user.id !== user.id)
|
||||
const deleteAccountFn = async(user) => await this.$store.dispatch('DeleteUser', user)
|
||||
const deleteAccountFn = async(users) => await this.$store.dispatch('DeleteUsers', users)
|
||||
|
||||
applyActionToAllUsers(filtered, deleteAccountFn)
|
||||
applyAction(filtered, deleteAccountFn)
|
||||
},
|
||||
addTag: (tag) => async() => {
|
||||
const filterUsersFn = user => tag === 'disable_remote_subscription' || tag === 'disable_any_subscription'
|
||||
? user.local && !user.tags.includes(tag)
|
||||
: !user.tags.includes(tag)
|
||||
const users = this.selectedUsers.filter(filterUsersFn)
|
||||
addTag: (tag) => () => {
|
||||
const filtered = this.selectedUsers.filter(user =>
|
||||
tag === 'disable_remote_subscription' || tag === 'disable_any_subscription'
|
||||
? user.local && !user.tags.includes(tag)
|
||||
: !user.tags.includes(tag))
|
||||
const addTagFn = async(users) => await this.$store.dispatch('AddTag', { users, tag })
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('AddTag', { users, tag })
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: this.$t('users.completed')
|
||||
})
|
||||
this.$emit('apply-action')
|
||||
applyAction(filtered, addTagFn)
|
||||
},
|
||||
removeTag: (tag) => async() => {
|
||||
const filterUsersFn = user => tag === 'disable_remote_subscription' || tag === 'disable_any_subscription'
|
||||
? user.local && user.tags.includes(tag)
|
||||
: user.tags.includes(tag)
|
||||
const users = this.selectedUsers.filter(filterUsersFn)
|
||||
const filtered = this.selectedUsers.filter(user =>
|
||||
tag === 'disable_remote_subscription' || tag === 'disable_any_subscription'
|
||||
? user.local && user.tags.includes(tag)
|
||||
: user.tags.includes(tag))
|
||||
const removeTagFn = async(users) => await this.$store.dispatch('RemoveTag', { users, tag })
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('RemoveTag', { users, tag })
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: this.$t('users.completed')
|
||||
})
|
||||
this.$emit('apply-action')
|
||||
applyAction(filtered, removeTagFn)
|
||||
},
|
||||
requirePasswordReset: () => {
|
||||
this.selectedUsers.map(user => this.$store.dispatch('RequirePasswordReset', user))
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
<el-dropdown-item
|
||||
v-if="showDeactivatedButton(scope.row.id)"
|
||||
:divided="showAdminAction(scope.row)"
|
||||
@click.native="handleDeactivation(scope.row)">
|
||||
@click.native="toggleActivation(scope.row)">
|
||||
{{ scope.row.deactivated ? $t('users.activateAccount') : $t('users.deactivateAccount') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
|
@ -275,11 +275,13 @@ export default {
|
|||
|
||||
this.$store.dispatch('RequirePasswordReset', { nickname })
|
||||
},
|
||||
handleDeactivation({ nickname }) {
|
||||
this.$store.dispatch('ToggleUserActivation', nickname)
|
||||
toggleActivation(user) {
|
||||
user.deactivated
|
||||
? this.$store.dispatch('ActivateUsers', [user])
|
||||
: this.$store.dispatch('DeactivateUsers', [user])
|
||||
},
|
||||
handleDeletion(user) {
|
||||
this.$store.dispatch('DeleteUser', user)
|
||||
this.$store.dispatch('DeleteUsers', [user])
|
||||
},
|
||||
handlePageChange(page) {
|
||||
const searchQuery = this.$store.state.users.searchQuery
|
||||
|
@ -308,7 +310,9 @@ export default {
|
|||
: this.$store.dispatch('AddTag', { users: [user], tag })
|
||||
},
|
||||
toggleUserRight(user, right) {
|
||||
this.$store.dispatch('ToggleRight', { user, right })
|
||||
user.roles[right]
|
||||
? this.$store.dispatch('DeleteRight', { users: [user], right })
|
||||
: this.$store.dispatch('AddRight', { users: [user], right })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ describe('Users actions', () => {
|
|||
store = new Vuex.Store(cloneDeep(storeConfig))
|
||||
})
|
||||
|
||||
it('grants admin and moderator rights to a local user', async (done) => {
|
||||
it('grants admin right to a local user', async (done) => {
|
||||
const wrapper = mount(Users, {
|
||||
store,
|
||||
localVue,
|
||||
|
@ -87,14 +87,28 @@ describe('Users actions', () => {
|
|||
const user = store.state.users.fetchedUsers[2]
|
||||
expect(user.roles.admin).toBe(false)
|
||||
expect(user.roles.moderator).toBe(false)
|
||||
|
||||
wrapper.find(htmlElement(3, 1)).trigger('click')
|
||||
await flushPromises()
|
||||
wrapper.find(htmlElement(3, 2)).trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser = store.state.users.fetchedUsers[2]
|
||||
expect(updatedUser.roles.admin).toBe(true)
|
||||
done()
|
||||
})
|
||||
|
||||
it('grants moderator right to a local user', async (done) => {
|
||||
const wrapper = mount(Users, {
|
||||
store,
|
||||
localVue,
|
||||
sync: false,
|
||||
stubs: ['router-link']
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
const user = store.state.users.fetchedUsers[2]
|
||||
expect(user.roles.admin).toBe(false)
|
||||
expect(user.roles.moderator).toBe(false)
|
||||
wrapper.find(htmlElement(3, 2)).trigger('click')
|
||||
|
||||
const updatedUser = store.state.users.fetchedUsers[2]
|
||||
expect(updatedUser.roles.moderator).toBe(true)
|
||||
done()
|
||||
})
|
||||
|
@ -126,9 +140,7 @@ describe('Users actions', () => {
|
|||
|
||||
const user = store.state.users.fetchedUsers[1]
|
||||
expect(user.deactivated).toBe(false)
|
||||
|
||||
wrapper.find(htmlElement(2, 1)).trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser = store.state.users.fetchedUsers[1]
|
||||
expect(updatedUser.deactivated).toBe(true)
|
||||
|
@ -166,9 +178,7 @@ describe('Users actions', () => {
|
|||
expect(user2.tags.length).toBe(1)
|
||||
|
||||
wrapper.find(htmlElement(1, 5)).trigger('click')
|
||||
await flushPromises()
|
||||
wrapper.find(htmlElement(2, 5)).trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser1 = store.state.users.fetchedUsers[0]
|
||||
const updatedUser2 = store.state.users.fetchedUsers[1]
|
||||
|
@ -188,33 +198,13 @@ describe('Users actions', () => {
|
|||
|
||||
const user = store.state.users.fetchedUsers[1]
|
||||
expect(user.tags.length).toBe(1)
|
||||
|
||||
wrapper.find(htmlElement(2, 6)).trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser = store.state.users.fetchedUsers[1]
|
||||
expect(updatedUser.tags.length).toBe(0)
|
||||
done()
|
||||
})
|
||||
|
||||
it('shows check icon when tag is added', async (done) => {
|
||||
const wrapper = mount(Users, {
|
||||
store,
|
||||
localVue,
|
||||
sync: false,
|
||||
stubs: ['router-link']
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find(`${htmlElement(1, 5)} i`).exists()).toBe(false)
|
||||
|
||||
wrapper.find(htmlElement(1, 5)).trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find(`${htmlElement(1, 5)} i`).exists()).toBe(true)
|
||||
done()
|
||||
})
|
||||
|
||||
it('does not change user index in array when tag is added', async (done) => {
|
||||
const wrapper = mount(Users, {
|
||||
store,
|
||||
|
|
|
@ -51,7 +51,6 @@ describe('Apply users actions to multiple users', () => {
|
|||
expect(user2.roles.admin).toBe(false)
|
||||
expect(user3.roles.admin).toBe(false)
|
||||
grantRight('admin')()
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser1 = store.state.users.fetchedUsers[0]
|
||||
const updatedUser2 = store.state.users.fetchedUsers[1]
|
||||
|
@ -88,7 +87,6 @@ describe('Apply users actions to multiple users', () => {
|
|||
expect(user2.roles.moderator).toBe(false)
|
||||
expect(user3.roles.moderator).toBe(false)
|
||||
grantRight('moderator')()
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser1 = store.state.users.fetchedUsers[0]
|
||||
const updatedUser2 = store.state.users.fetchedUsers[1]
|
||||
|
@ -123,7 +121,6 @@ describe('Apply users actions to multiple users', () => {
|
|||
expect(user1.roles.admin).toBe(true)
|
||||
expect(user2.roles.admin).toBe(false)
|
||||
revokeRight('admin')()
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser1 = store.state.users.fetchedUsers[0]
|
||||
const updatedUser2 = store.state.users.fetchedUsers[2]
|
||||
|
@ -173,7 +170,6 @@ describe('Apply users actions to multiple users', () => {
|
|||
const user = store.state.users.fetchedUsers[2]
|
||||
expect(user.deactivated).toBe(true)
|
||||
activate()
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser = store.state.users.fetchedUsers[2]
|
||||
expect(updatedUser.deactivated).toBe(false)
|
||||
|
@ -203,7 +199,6 @@ describe('Apply users actions to multiple users', () => {
|
|||
expect(user1.deactivated).toBe(false)
|
||||
expect(user2.deactivated).toBe(false)
|
||||
deactivate()
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser1 = store.state.users.fetchedUsers[0]
|
||||
const updatedUser2 = store.state.users.fetchedUsers[1]
|
||||
|
@ -270,7 +265,6 @@ describe('Apply users actions to multiple users', () => {
|
|||
expect(user1.tags.length).toBe(0)
|
||||
expect(user2.tags.length).toBe(1)
|
||||
addTag('strip_media')()
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser1 = store.state.users.fetchedUsers[0]
|
||||
const updatedUser2 = store.state.users.fetchedUsers[1]
|
||||
|
@ -311,7 +305,6 @@ describe('Apply users actions to multiple users', () => {
|
|||
expect(user1.tags.length).toBe(1)
|
||||
expect(user2.tags.length).toBe(1)
|
||||
removeTag('strip_media')()
|
||||
await flushPromises()
|
||||
|
||||
const updatedUser1 = store.state.users.fetchedUsers[1]
|
||||
const updatedUser2 = store.state.users.fetchedUsers[2]
|
||||
|
|
Loading…
Reference in a new issue