Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • pleroma/admin-fe
  • linafilippova/admin-fe
  • Exilat_a_Tolosa/admin-fe
  • mkljczk/admin-fe
  • maxf/admin-fe
  • kphrx/admin-fe
  • vaartis/admin-fe
  • ELR/admin-fe
  • eugenijm/admin-fe
  • jp/admin-fe
  • mkfain/admin-fe
  • lorenzoancora/admin-fe
  • alexgleason/admin-fe
  • seanking/admin-fe
  • ilja/admin-fe
15 results
Show changes
Commits on Source (57)
Showing
with 250 additions and 533 deletions
......@@ -8,7 +8,22 @@ 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
- Leave dropdown menu open after clicking an action
- Move current try/catch error handling from view files to module, add it where necessary
### Added
- Optimistic update for actions in users module and fetching users after api function finished its execution
- Relay management
### Fixed
- Show checkmarks when tag is applied
- Reports update (also, now it's optimistic)
## [1.2.0] - 2019-09-27
......
......@@ -8,7 +8,6 @@
<title>Admin FE</title>
</head>
<body>
<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
......
......@@ -11,20 +11,42 @@ const reports = [
{ created_at: '2019-05-18T13:01:33.000Z', account: { acct: 'nick', display_name: 'Nick Keys', tags: [] }, actor: { acct: 'admin' }, state: 'closed', id: '4', content: '', statuses: [] }
]
export async function fetchReports(limit, max_id, authHost, token) {
const paginatedReports = max_id.length > 0 ? reports.slice(5) : reports.slice(0, 5)
return Promise.resolve({ data: { reports: paginatedReports }})
const groupedReports = [
{ account: { avatar: 'http://localhost:4000/images/avi.png', confirmation_pending: false, deactivated: false, display_name: 'leo', id: '9oG0YghgBi94EATI9I', local: true, nickname: 'leo', roles: { admin: false, moderator: false }, tags: [] },
actors: [{ acct: 'admin', avatar: 'http://localhost:4000/images/avi.png', deactivated: false, display_name: 'admin', id: '9oFz4pTauG0cnJ581w', local: true, nickname: 'admin', roles: { admin: false, moderator: false }, tags: [], url: 'http://localhost:4000/users/admin', username: 'admin' }],
date: '2019-11-23T12:56:11.969772Z',
reports: [
{ created_at: '2019-05-21T21:35:33.000Z', account: { acct: 'benj', display_name: 'Benjamin Fame', tags: [] }, actor: { acct: 'admin' }, state: 'open', id: '2', content: 'This is a report', statuses: [] },
{ created_at: '2019-05-20T22:45:33.000Z', account: { acct: 'alice', display_name: 'Alice Pool', tags: [] }, actor: { acct: 'admin2' }, state: 'resolved', id: '7', content: 'Please block this user', statuses: [
{ account: { display_name: 'Alice Pool', avatar: '' }, visibility: 'public', sensitive: false, id: '11', content: 'Hey!', url: '', created_at: '2019-05-10T21:35:33.000Z' },
{ account: { display_name: 'Alice Pool', avatar: '' }, visibility: 'unlisted', sensitive: true, id: '10', content: 'Bye!', url: '', created_at: '2019-05-10T21:00:33.000Z' }
] }
],
status: {
account: { acct: 'leo' },
content: 'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis',
created_at: '2019-11-23T12:55:20.000Z',
id: '9pFoQO69piu7cUDnJg',
url: 'http://localhost:4000/notice/9pFoQO69piu7cUDnJg',
visibility: 'unlisted',
sensitive: true
},
status_deleted: false
}
]
export async function fetchReports(filter, page, pageSize, authHost, token) {
return filter.length > 0
? Promise.resolve({ data: { reports: reports.filter(report => report.state === filter) }})
: Promise.resolve({ data: { reports }})
}
export async function filterReports(filter, limit, max_id, authHost, token) {
const filteredReports = reports.filter(report => report.state === filter)
const paginatedReports = max_id.length > 0 ? filteredReports.slice(5) : filteredReports.slice(0, 5)
return Promise.resolve({ data: { reports: paginatedReports }})
export async function fetchGroupedReports(authHost, token) {
return Promise.resolve({ data: { reports: groupedReports }})
}
export async function changeState(state, id, authHost, token) {
const report = reports.find(report => report.id === id)
return Promise.resolve({ data: { ...report, state }})
export async function changeState(reportsData, authHost, token) {
return Promise.resolve({ data: '' })
}
export async function changeStatusScope(id, sensitive, visibility, authHost, token) {
......
export async function changeStatusScope(id, sensitive, visibility, authHost, token) {
return Promise.resolve()
}
export async function deleteStatus(id, authHost, token) {
return Promise.resolve()
}
......@@ -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
})
}
......
File moved
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'
]] }
]
}
]
import request from '@/utils/request'
import { getToken } from '@/utils/auth'
import { baseName } from './utils'
export async function fetchRelays(authHost, token) {
return await request({
baseURL: baseName(authHost),
url: '/api/pleroma/admin/relay',
method: 'get',
headers: authHeaders(token)
})
}
export async function addRelay(relay, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: '/api/pleroma/admin/relay',
method: 'post',
headers: authHeaders(token),
data: { relay_url: relay }
})
}
export async function deleteRelay(relay, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: '/api/pleroma/admin/relay',
method: 'delete',
headers: authHeaders(token),
data: { relay_url: `https://${relay}/actor` }
})
}
const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {}
......@@ -2,48 +2,32 @@ import request from '@/utils/request'
import { getToken } from '@/utils/auth'
import { baseName } from './utils'
export async function changeState(state, id, authHost, token) {
export async function changeState(reports, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/reports/${id}`,
method: 'put',
url: `/api/pleroma/admin/reports`,
method: 'patch',
headers: authHeaders(token),
data: { state }
data: { reports }
})
}
export async function changeStatusScope(id, sensitive, visibility, authHost, token) {
export async function fetchReports(filter, page, pageSize, authHost, token) {
const url = filter.length > 0
? `/api/pleroma/admin/reports?state=${filter}&page=${page}&page_size=${pageSize}`
: `/api/pleroma/admin/reports?page=${page}&page_size=${pageSize}`
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/statuses/${id}`,
method: 'put',
headers: authHeaders(token),
data: { sensitive, visibility }
})
}
export async function deleteStatus(id, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/statuses/${id}`,
method: 'delete',
headers: authHeaders(token)
})
}
export async function fetchReports(limit, max_id, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/reports?limit=${limit}&max_id=${max_id}`,
url,
method: 'get',
headers: authHeaders(token)
})
}
export async function filterReports(filter, limit, max_id, authHost, token) {
export async function fetchGroupedReports(authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/reports?state=${filter}&limit=${limit}&max_id=${max_id}`,
url: `/api/pleroma/admin/grouped_reports`,
method: 'get',
headers: authHeaders(token)
})
......
import request from '@/utils/request'
import { getToken } from '@/utils/auth'
import { baseName } from './utils'
export async function changeStatusScope(id, sensitive, visibility, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/statuses/${id}`,
method: 'put',
headers: authHeaders(token),
data: { sensitive, visibility }
})
}
export async function deleteStatus(id, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/statuses/${id}`,
method: 'delete',
headers: authHeaders(token)
})
}
const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {}
......@@ -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}`,
url: `/api/pleroma/admin/users/deactivate`,
method: 'patch',
headers: authHeaders(token),
data: { nicknames }
})
}
export async function deleteRight(nicknames, right, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users/permission_group/${right}`,
method: 'delete',
headers: authHeaders(token)
headers: authHeaders(token),
data: { nicknames }
})
}
export async function deleteUser(nickname, authHost, token) {
export async function deleteUsers(nicknames, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users?nickname=${nickname}`,
url: `/api/pleroma/admin/users`,
method: 'delete',
headers: authHeaders(token)
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),
......
<template>
<div class="upload-container">
<el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">上传图片
</el-button>
<el-dialog :visible.sync="dialogVisible">
<el-upload
:multiple="true"
:file-list="fileList"
:show-file-list="true"
:on-remove="handleRemove"
:on-success="handleSuccess"
:before-upload="beforeUpload"
class="editor-slide-upload"
action="https://httpbin.org/post"
list-type="picture-card">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="handleSubmit">确 定</el-button>
</el-dialog>
</div>
</template>
<script>
// import { getToken } from 'api/qiniu'
export default {
name: 'EditorSlideUpload',
props: {
color: {
type: String,
default: '#1890ff'
}
},
data: function() {
return {
dialogVisible: false,
listObj: {},
fileList: []
}
},
methods: {
checkAllSuccess() {
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
},
handleSubmit() {
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
if (!this.checkAllSuccess()) {
this.$message('请等待所有图片上传成功 或 出现了网络问题,请刷新页面重新上传!')
return
}
this.$emit('successCBK', arr)
this.listObj = {}
this.fileList = []
this.dialogVisible = false
},
handleSuccess(response, file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
this.listObj[objKeyArr[i]].url = response.files.file
this.listObj[objKeyArr[i]].hasSuccess = true
return
}
}
},
handleRemove(file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
delete this.listObj[objKeyArr[i]]
return
}
}
},
beforeUpload(file) {
const _self = this
const _URL = window.URL || window.webkitURL
const fileName = file.uid
this.listObj[fileName] = {}
return new Promise((resolve, reject) => {
const img = new Image()
img.src = _URL.createObjectURL(file)
img.onload = function() {
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
}
resolve(true)
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.editor-slide-upload {
margin-bottom: 20px;
/deep/ .el-upload--picture-card {
width: 100%;
}
}
</style>
<template>
<div :class="{fullscreen:fullscreen}" class="tinymce-container editor-container">
<textarea :id="tinymceId" class="tinymce-textarea"/>
<div class="editor-custom-btn-container">
<editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK"/>
</div>
</div>
</template>
<script>
import editorImage from './components/editorImage'
import plugins from './plugins'
import toolbar from './toolbar'
export default {
name: 'Tinymce',
components: { editorImage },
props: {
id: {
type: String,
default: function() {
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
value: {
type: String,
default: ''
},
toolbar: {
type: Array,
required: false,
default() {
return []
}
},
menubar: {
type: String,
default: 'file edit insert view format table'
},
height: {
type: Number,
required: false,
default: 360
}
},
data: function() {
return {
hasChange: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
'en': 'en',
'zh': 'zh_CN'
}
}
},
computed: {
language() {
return this.languageTypeList[this.$store.getters.language]
}
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || ''))
}
},
language() {
this.destroyTinymce()
this.$nextTick(() => this.initTinymce())
}
},
mounted() {
this.initTinymce()
},
activated() {
this.initTinymce()
},
deactivated() {
this.destroyTinymce()
},
destroyed() {
this.destroyTinymce()
},
methods: {
initTinymce() {
const _this = this
window.tinymce.init({
language: this.language,
selector: `#${this.tinymceId}`,
height: this.height,
body_class: 'panel-body ',
object_resizing: false,
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
end_container_on_empty_block: true,
powerpaste_word_import: 'clean',
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: 'square',
advlist_number_styles: 'default',
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
default_link_target: '_blank',
link_title: false,
nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
init_instance_callback: editor => {
if (_this.value) {
editor.setContent(_this.value)
}
_this.hasInit = true
editor.on('NodeChange Change KeyUp SetContent', () => {
this.hasChange = true
this.$emit('input', editor.getContent())
})
},
setup(editor) {
editor.on('FullscreenStateChanged', (e) => {
_this.fullscreen = e.state
})
}
// 整合七牛上传
// images_dataimg_filter(img) {
// setTimeout(() => {
// const $image = $(img);
// $image.removeAttr('width');
// $image.removeAttr('height');
// if ($image[0].height && $image[0].width) {
// $image.attr('data-wscntype', 'image');
// $image.attr('data-wscnh', $image[0].height);
// $image.attr('data-wscnw', $image[0].width);
// $image.addClass('wscnph');
// }
// }, 0);
// return img
// },
// images_upload_handler(blobInfo, success, failure, progress) {
// progress(0);
// const token = _this.$store.getters.token;
// getToken(token).then(response => {
// const url = response.data.qiniu_url;
// const formData = new FormData();
// formData.append('token', response.data.qiniu_token);
// formData.append('key', response.data.qiniu_key);
// formData.append('file', blobInfo.blob(), url);
// upload(formData).then(() => {
// success(url);
// progress(100);
// })
// }).catch(err => {
// failure('出现未知问题,刷新页面,或者联系程序员')
// console.log(err);
// });
// },
})
},
destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId)
if (this.fullscreen) {
tinymce.execCommand('mceFullScreen')
}
if (tinymce) {
tinymce.destroy()
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value)
},
getContent() {
window.tinymce.get(this.tinymceId).getContent()
},
imageSuccessCBK(arr) {
const _this = this
arr.forEach(v => {
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)
})
}
}
}
</script>
<style scoped>
.tinymce-container {
position: relative;
line-height: normal;
}
.tinymce-container>>>.mce-fullscreen {
z-index: 10000;
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 4px;
top: 4px;
/*z-index: 2005;*/
}
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
}
.editor-upload-btn {
display: inline-block;
}
</style>
// Any plugins you want to use has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/
const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
export default plugins
// Here is a list of the toolbar
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
export default toolbar
......@@ -10,7 +10,6 @@ export default {
icons: 'Icons',
components: 'Components',
componentIndex: 'Introduction',
tinymce: 'Tinymce',
markdown: 'Markdown',
jsonEditor: 'JSON Editor',
dndList: 'Dnd List',
......@@ -105,7 +104,6 @@ export default {
},
components: {
documentation: 'Documentation',
tinymceTips: 'Rich text editor is a core part of management system, but at the same time is a place with lots of problems. In the process of selecting rich texts, I also walked a lot of detours. The common rich text editors in the market are basically used, and the finally chose Tinymce. See documentation for more detailed rich text editor comparisons and introductions.',
dropzoneTips: 'Because my business has special needs, and has to upload images to qiniu, so instead of a third party, I chose encapsulate it by myself. It is very simple, you can see the detail code in @/components/Dropzone.',
stickyTips: 'when the page is scrolled to the preset position will be sticky on the top.',
backToTopTips1: 'When the page is scrolled to the specified position, the Back to Top button appears in the lower right corner',
......@@ -255,6 +253,7 @@ export default {
},
reports: {
reports: 'Reports',
groupedReports: 'Grouped reports',
reply: 'Reply',
from: 'From',
showNotes: 'Show notes',
......@@ -266,19 +265,32 @@ export default {
deleteCompleted: 'Delete comleted',
deleteCanceled: 'Delete canceled',
noNotes: 'No notes to display',
changeState: 'Change report state',
changeState: "Change report's state",
changeAllReports: 'Change all reports',
changeScope: 'Change scope',
moderateUser: 'Moderate user',
resolve: 'Resolve',
reopen: 'Reopen',
close: 'Close',
resolveAll: 'Resolve all',
reopenAll: 'Reopen all',
closeAll: 'Close all',
addSensitive: 'Add Sensitive flag',
removeSensitive: 'Remove Sensitive flag',
public: 'Make status public',
private: 'Make status private',
unlisted: 'Make status unlisted',
sensitive: 'Sensitive',
deleteStatus: 'Delete status'
deleteStatus: 'Delete status',
reportOn: 'Report on',
reportsOn: 'Reports on',
id: 'ID',
account: 'Account',
actor: 'Actor',
actors: 'Actors',
content: 'Content',
reportedStatus: 'Reported status',
statusDeleted: 'This status has been deleted'
},
reportsFilter: {
inputPlaceholder: 'Select filter',
......@@ -312,7 +324,66 @@ export default {
rateLimiters: 'Rate limiters',
database: 'Database',
other: 'Other',
success: 'Settings changed successfully!'
relays: 'Relays',
follow: 'Follow',
followRelay: 'Follow new relay',
instanceUrl: 'Instance URL',
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',
successfullyDownloaded: 'Successfully downloaded',
successfullyImported: 'Successfully imported',
nowNewPacksToImport: 'No new packs to import',
successfullyUpdated: 'Successfully updated',
metadatLowerCase: 'metadata',
files: 'files'
},
invites: {
inviteTokens: 'Invite tokens',
......
......@@ -10,7 +10,6 @@ export default {
icons: 'Iconos',
components: 'Componentes',
componentIndex: 'Introducción',
tinymce: 'Tinymce',
markdown: 'Markdown',
jsonEditor: 'Editor JSON',
dndList: 'Lista Dnd',
......@@ -96,7 +95,6 @@ export default {
},
components: {
documentation: 'Documentación',
tinymceTips: 'Rich text editor is a core part of management system, but at the same time is a place with lots of problems. In the process of selecting rich texts, I also walked a lot of detours. The common rich text editors in the market are basically used, and the finally chose Tinymce. See documentation for more detailed rich text editor comparisons and introductions.',
dropzoneTips: 'Because my business has special needs, and has to upload images to qiniu, so instead of a third party, I chose encapsulate it by myself. It is very simple, you can see the detail code in @/components/Dropzone.',
stickyTips: 'when the page is scrolled to the preset position will be sticky on the top.',
backToTopTips1: 'When the page is scrolled to the specified position, the Back to Top button appears in the lower right corner',
......
......@@ -10,7 +10,6 @@ export default {
icons: 'Icònas',
components: 'Compausants',
componentIndex: 'Introduccion',
tinymce: 'Tinymce',
markdown: 'Markdown',
jsonEditor: 'JSON Editor',
dndList: 'Dnd List',
......@@ -97,7 +96,6 @@ export default {
},
components: {
documentation: 'Documentacion',
tinymceTips: 'Rich text editor is a core part of management system, but at the same time is a place with lots of problems. In the process of selecting rich texts, I also walked a lot of detours. The common rich text editors in the market are basically used, and the finally chose Tinymce. See documentation for more detailed rich text editor comparisons and introductions.',
dropzoneTips: 'Because my business has special needs, and has to upload images to qiniu, so instead of a third party, I chose encapsulate it by myself. It is very simple, you can see the detail code in @/components/Dropzone.',
stickyTips: 'when the page is scrolled to the preset position will be sticky on the top.',
backToTopTips1: 'When the page is scrolled to the specified position, the Back to Top button appears in the lower right corner',
......
......@@ -10,7 +10,6 @@ export default {
icons: '图标',
components: '组件',
componentIndex: '介绍',
tinymce: '富文本编辑器',
markdown: 'Markdown',
jsonEditor: 'JSON编辑器',
dndList: '列表拖拽',
......@@ -96,7 +95,6 @@ export default {
},
components: {
documentation: '文档',
tinymceTips: '富文本是管理后台一个核心的功能,但同时又是一个有很多坑的地方。在选择富文本的过程中我也走了不少的弯路,市面上常见的富文本都基本用过了,最终权衡了一下选择了Tinymce。更详细的富文本比较和介绍见',
dropzoneTips: '由于我司业务有特殊需求,而且要传七牛 所以没用第三方,选择了自己封装。代码非常的简单,具体代码你可以在这里看到 @/components/Dropzone',
stickyTips: '当页面滚动到预设的位置会吸附在顶部',
backToTopTips1: '页面滚动到指定位置会在右下角出现返回顶部按钮',
......
......@@ -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,
......