Merge remote-tracking branch 'upstream/develop' into shigusegubu
* upstream/develop: (59 commits) Fix pipelines and clean up console output switch from method to computed property formatting add tags to data and to status component Remove auto-hyphenation make staff label visible move visibility-tray css in local scope refactor css make only screen name as link refactor css for visibility tray moved setting styles into common to avoid bug with shared styles hide three dot menu button if has no items Eliminate automatic zooming on mobile entity normalizer: add tooltip text to emojis rename for consistency's sake update admin api urls in accordance with new docs Line up rich text format picker with the status form fix lint Cleanup, little documentation, localization update api service functions ...
This commit is contained in:
commit
30a89201ca
53 changed files with 549 additions and 267 deletions
|
@ -58,7 +58,5 @@ exports.cssLoaders = function (options) {
|
|||
|
||||
// Generate loaders for standalone style files (outside of .vue)
|
||||
exports.styleLoaders = function (options) {
|
||||
var output = exports.cssLoaders(options)
|
||||
console.log(output)
|
||||
return output
|
||||
return exports.cssLoaders(options)
|
||||
}
|
||||
|
|
|
@ -14,8 +14,6 @@ let commitHash = require('child_process')
|
|||
.execSync('git rev-parse --short HEAD')
|
||||
.toString();
|
||||
|
||||
console.log(commitHash)
|
||||
|
||||
var webpackConfig = merge(baseWebpackConfig, {
|
||||
mode: 'production',
|
||||
module: {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
|
||||
<title>Pleroma</title>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/ngs/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/ngs/favicon-32x32.png">
|
||||
|
|
79
src/App.scss
79
src/App.scss
|
@ -625,21 +625,6 @@ nav {
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.visibility-tray {
|
||||
font-size: 1.2em;
|
||||
padding: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
.selected {
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
}
|
||||
|
||||
div {
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.visibility-notice {
|
||||
padding: .5em;
|
||||
border: 1px solid $fallback--faint;
|
||||
|
@ -740,6 +725,70 @@ nav {
|
|||
}
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
border-bottom: 2px solid var(--fg, $fallback--fg);
|
||||
margin: 1em 1em 1.4em;
|
||||
padding-bottom: 1.4em;
|
||||
|
||||
> div {
|
||||
margin-bottom: .5em;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
select {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.unavailable,
|
||||
.unavailable i {
|
||||
color: var(--cRed, $fallback--cRed);
|
||||
color: $fallback--cRed;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-height: 28px;
|
||||
min-width: 10em;
|
||||
padding: 0 2em;
|
||||
}
|
||||
|
||||
.number-input {
|
||||
max-width: 6em;
|
||||
}
|
||||
}
|
||||
.select-multiple {
|
||||
display: flex;
|
||||
.option-list {
|
||||
margin: 0;
|
||||
padding-left: .5em;
|
||||
}
|
||||
}
|
||||
.setting-list,
|
||||
.option-list{
|
||||
list-style-type: none;
|
||||
padding-left: 2em;
|
||||
li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.suboptions {
|
||||
margin-top: 0.3em
|
||||
}
|
||||
}
|
||||
|
||||
.login-hint {
|
||||
text-align: center;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import PublicAndExternalTimeline from 'components/public_and_external_timeline/p
|
|||
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
|
||||
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
|
||||
import ConversationPage from 'components/conversation-page/conversation-page.vue'
|
||||
import Mentions from 'components/mentions/mentions.vue'
|
||||
import Interactions from 'components/interactions/interactions.vue'
|
||||
import DMs from 'components/dm_timeline/dm_timeline.vue'
|
||||
import UserProfile from 'components/user_profile/user_profile.vue'
|
||||
import Settings from 'components/settings/settings.vue'
|
||||
|
@ -34,7 +34,7 @@ export default (store) => {
|
|||
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
|
||||
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
|
||||
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile },
|
||||
{ name: 'mentions', path: '/users/:username/mentions', component: Mentions },
|
||||
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions },
|
||||
{ name: 'dms', path: '/users/:username/dms', component: DMs },
|
||||
{ name: 'settings', path: '/settings', component: Settings },
|
||||
{ name: 'registration', path: '/registration', component: Registration },
|
||||
|
|
|
@ -41,7 +41,8 @@ const conversation = {
|
|||
props: [
|
||||
'statusoid',
|
||||
'collapsable',
|
||||
'isPage'
|
||||
'isPage',
|
||||
'showPinned'
|
||||
],
|
||||
created () {
|
||||
if (this.isPage) {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
:inlineExpanded="collapsable && isExpanded"
|
||||
:statusoid="status"
|
||||
:expandable='!isExpanded'
|
||||
:showPinned="showPinned"
|
||||
:focused="focused(status.id)"
|
||||
:inConversation="isExpanded"
|
||||
:highlight="getHighlight()"
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
const DeleteButton = {
|
||||
props: [ 'status' ],
|
||||
methods: {
|
||||
deleteStatus () {
|
||||
const confirmed = window.confirm('Do you really want to delete this status?')
|
||||
if (confirmed) {
|
||||
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentUser () { return this.$store.state.users.currentUser },
|
||||
canDelete () {
|
||||
if (!this.currentUser) { return }
|
||||
const superuser = this.currentUser.rights.moderator || this.currentUser.rights.admin
|
||||
return superuser || this.status.user.id === this.currentUser.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DeleteButton
|
|
@ -1,21 +0,0 @@
|
|||
<template>
|
||||
<div v-if="canDelete">
|
||||
<a href="#" v-on:click.prevent="deleteStatus()">
|
||||
<i class='button-icon icon-cancel delete-status'></i>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./delete_button.js" ></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.icon-cancel,.delete-status {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $fallback--cRed;
|
||||
color: var(--cRed, $fallback--cRed);
|
||||
}
|
||||
}
|
||||
</style>
|
64
src/components/extra_buttons/extra_buttons.js
Normal file
64
src/components/extra_buttons/extra_buttons.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
import Popper from 'vue-popperjs/src/component/popper.js.vue'
|
||||
|
||||
const ExtraButtons = {
|
||||
props: [ 'status' ],
|
||||
components: {
|
||||
Popper
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showDropDown: false,
|
||||
showPopper: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteStatus () {
|
||||
this.refreshPopper()
|
||||
const confirmed = window.confirm(this.$t('status.delete_confirm'))
|
||||
if (confirmed) {
|
||||
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
||||
}
|
||||
},
|
||||
toggleMenu () {
|
||||
this.showDropDown = !this.showDropDown
|
||||
},
|
||||
pinStatus () {
|
||||
this.refreshPopper()
|
||||
this.$store.dispatch('pinStatus', this.status.id)
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
.catch(err => this.$emit('onError', err.error.error))
|
||||
},
|
||||
unpinStatus () {
|
||||
this.refreshPopper()
|
||||
this.$store.dispatch('unpinStatus', this.status.id)
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
.catch(err => this.$emit('onError', err.error.error))
|
||||
},
|
||||
refreshPopper () {
|
||||
this.showPopper = false
|
||||
this.showDropDown = false
|
||||
setTimeout(() => {
|
||||
this.showPopper = true
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentUser () { return this.$store.state.users.currentUser },
|
||||
canDelete () {
|
||||
if (!this.currentUser) { return }
|
||||
const superuser = this.currentUser.rights.moderator || this.currentUser.rights.admin
|
||||
return superuser || this.status.user.id === this.currentUser.id
|
||||
},
|
||||
ownStatus () {
|
||||
return this.status.user.id === this.currentUser.id
|
||||
},
|
||||
canPin () {
|
||||
return this.ownStatus && (this.status.visibility === 'public' || this.status.visibility === 'unlisted')
|
||||
},
|
||||
enabled () {
|
||||
return this.canPin || this.canDelete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ExtraButtons
|
47
src/components/extra_buttons/extra_buttons.vue
Normal file
47
src/components/extra_buttons/extra_buttons.vue
Normal file
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<Popper
|
||||
trigger="click"
|
||||
@hide='showDropDown = false'
|
||||
append-to-body
|
||||
v-if="enabled && showPopper"
|
||||
:options="{
|
||||
placement: 'top',
|
||||
modifiers: {
|
||||
arrow: { enabled: true },
|
||||
offset: { offset: '0, 5px' },
|
||||
}
|
||||
}"
|
||||
>
|
||||
<div class="popper-wrapper">
|
||||
<div class="dropdown-menu">
|
||||
<button class="dropdown-item dropdown-item-icon" @click.prevent="pinStatus" v-if="!status.pinned && canPin">
|
||||
<i class="icon-pin"></i><span>{{$t("status.pin")}}</span>
|
||||
</button>
|
||||
<button class="dropdown-item dropdown-item-icon" @click.prevent="unpinStatus" v-if="status.pinned && canPin">
|
||||
<i class="icon-pin"></i><span>{{$t("status.unpin")}}</span>
|
||||
</button>
|
||||
<button class="dropdown-item dropdown-item-icon" @click.prevent="deleteStatus" v-if="canDelete">
|
||||
<i class="icon-cancel"></i><span>{{$t("status.delete")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-icon" slot="reference" @click="toggleMenu">
|
||||
<i class='icon-ellipsis' :class="{'icon-clicked': showDropDown}"></i>
|
||||
</div>
|
||||
</Popper>
|
||||
</template>
|
||||
|
||||
<script src="./extra_buttons.js" ></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.icon-ellipsis {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, &.icon-clicked {
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
}
|
||||
</style>
|
25
src/components/interactions/interactions.js
Normal file
25
src/components/interactions/interactions.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import Notifications from '../notifications/notifications.vue'
|
||||
|
||||
const tabModeDict = {
|
||||
mentions: ['mention'],
|
||||
'likes+repeats': ['repeat', 'like'],
|
||||
follows: ['follow']
|
||||
}
|
||||
|
||||
const Interactions = {
|
||||
data () {
|
||||
return {
|
||||
filterMode: tabModeDict['mentions']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onModeSwitch (index, dataset) {
|
||||
this.filterMode = tabModeDict[dataset.filter]
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Notifications
|
||||
}
|
||||
}
|
||||
|
||||
export default Interactions
|
25
src/components/interactions/interactions.vue
Normal file
25
src/components/interactions/interactions.vue
Normal file
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="title">
|
||||
Interactions
|
||||
</div>
|
||||
</div>
|
||||
<tab-switcher
|
||||
ref="tabSwitcher"
|
||||
:onSwitch="onModeSwitch"
|
||||
>
|
||||
<span data-tab-dummy data-filter="mentions" :label="$t('nav.mentions')"/>
|
||||
<span data-tab-dummy data-filter="likes+repeats" :label="$t('interactions.favs_repeats')"/>
|
||||
<span data-tab-dummy data-filter="follows" :label="$t('interactions.follows')"/>
|
||||
</tab-switcher>
|
||||
<Notifications
|
||||
ref="notifications"
|
||||
:noHeading="true"
|
||||
:minimalMode="true"
|
||||
:filterMode="filterMode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./interactions.js"></script>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<Timeline :title="$t('nav.mentions')" v-bind:timeline="timeline" v-bind:timeline-name="'mentions'"/>
|
||||
<Timeline :title="$t('nav.interactions')" v-bind:timeline="timeline" v-bind:timeline-name="'mentions'"/>
|
||||
</template>
|
||||
|
||||
<script src="./mentions.js"></script>
|
||||
|
|
|
@ -127,6 +127,14 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&-icon {
|
||||
padding-left: 0.5rem;
|
||||
|
||||
i {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
// TODO: improve the look on breeze themes
|
||||
background-color: $fallback--fg;
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
</router-link>
|
||||
</li>
|
||||
<li v-if='currentUser'>
|
||||
<router-link :to="{ name: 'mentions', params: { username: currentUser.screen_name } }">
|
||||
{{ $t("nav.mentions") }}
|
||||
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
|
||||
{{ $t("nav.interactions") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if='currentUser'>
|
||||
|
|
|
@ -7,15 +7,24 @@ import {
|
|||
} from '../../services/notification_utils/notification_utils.js'
|
||||
|
||||
const Notifications = {
|
||||
props: [
|
||||
'noHeading'
|
||||
],
|
||||
props: {
|
||||
// Disables display of panel header
|
||||
noHeading: Boolean,
|
||||
// Disables panel styles, unread mark, potentially other notification-related actions
|
||||
// meant for "Interactions" timeline
|
||||
minimalMode: Boolean,
|
||||
// Custom filter mode, an array of strings, possible values 'mention', 'repeat', 'like', 'follow', used to override global filter for use in "Interactions" timeline
|
||||
filterMode: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
bottomedOut: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mainClass () {
|
||||
return this.minimalMode ? '' : 'panel panel-default'
|
||||
},
|
||||
notifications () {
|
||||
return notificationsFromStore(this.$store)
|
||||
},
|
||||
|
@ -26,7 +35,8 @@ const Notifications = {
|
|||
return unseenNotificationsFromStore(this.$store)
|
||||
},
|
||||
visibleNotifications () {
|
||||
return visibleNotificationsFromStore(this.$store)
|
||||
console.log(this.filterMode)
|
||||
return visibleNotificationsFromStore(this.$store, this.filterMode)
|
||||
},
|
||||
unseenCount () {
|
||||
return this.unseenNotifications.length
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.notifications {
|
||||
// a bit of a hack to allow scrolling below notifications
|
||||
padding-bottom: 15em;
|
||||
&:not(.minimal) {
|
||||
// a bit of a hack to allow scrolling below notifications
|
||||
padding-bottom: 15em;
|
||||
}
|
||||
|
||||
.loadmore-error {
|
||||
color: $fallback--text;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="notifications">
|
||||
<div class="panel panel-default">
|
||||
<div :class="{ minimal: minimalMode }" class="notifications">
|
||||
<div :class="mainClass">
|
||||
<div v-if="!noHeading" class="panel-heading">
|
||||
<div class="title">
|
||||
{{$t('notifications.notifications')}}
|
||||
|
@ -12,7 +12,7 @@
|
|||
<button v-if="unseenCount" @click.prevent="markAsSeen" class="read-button">{{$t('notifications.read')}}</button>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div v-for="notification in visibleNotifications" :key="notification.id" class="notification" :class='{"unseen": !notification.seen}'>
|
||||
<div v-for="notification in visibleNotifications" :key="notification.id" class="notification" :class='{"unseen": !minimalMode && !notification.seen}'>
|
||||
<div class="notification-overlay"></div>
|
||||
<notification :notification="notification"></notification>
|
||||
</div>
|
||||
|
@ -22,7 +22,9 @@
|
|||
{{$t('notifications.no_more_notifications')}}
|
||||
</div>
|
||||
<a v-else-if="!loading" href="#" v-on:click.prevent="fetchOlderNotifications()">
|
||||
<div class="new-status-notification text-center panel-footer">{{$t('notifications.load_older')}}</div>
|
||||
<div class="new-status-notification text-center panel-footer">
|
||||
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older')}}
|
||||
</div>
|
||||
</a>
|
||||
<div v-else class="new-status-notification text-center panel-footer">
|
||||
<i class="icon-spin3 animate-spin"/>
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
>
|
||||
</textarea>
|
||||
<div class="visibility-tray">
|
||||
<span class="text-format" v-if="formattingOptionsEnabled">
|
||||
<div class="text-format" v-if="formattingOptionsEnabled">
|
||||
<label for="post-content-type" class="select">
|
||||
<select id="post-content-type" v-model="newStatus.contentType" class="form-control">
|
||||
<option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat">
|
||||
|
@ -67,7 +67,7 @@
|
|||
</select>
|
||||
<i class="icon-down-open"></i>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<scope-selector
|
||||
:showAll="showAllScopes"
|
||||
|
@ -152,6 +152,7 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row-reverse;
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,7 +251,7 @@
|
|||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.3em 0.5em 0.6em;
|
||||
padding: 0.25em 0.5em 0.5em;
|
||||
line-height:24px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-if="!showNothing">
|
||||
<div v-if="!showNothing" class="scope-selector">
|
||||
<i class="icon-mail-alt"
|
||||
:class="css.direct"
|
||||
:title="$t('post_status.scope.direct')"
|
||||
|
@ -28,3 +28,19 @@
|
|||
</template>
|
||||
|
||||
<script src="./scope_selector.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.scope-selector {
|
||||
i {
|
||||
font-size: 1.2em;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -303,71 +303,3 @@
|
|||
|
||||
<script src="./settings.js">
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.setting-item {
|
||||
border-bottom: 2px solid var(--fg, $fallback--fg);
|
||||
margin: 1em 1em 1.4em;
|
||||
padding-bottom: 1.4em;
|
||||
|
||||
> div {
|
||||
margin-bottom: .5em;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
select {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.unavailable,
|
||||
.unavailable i {
|
||||
color: var(--cRed, $fallback--cRed);
|
||||
color: $fallback--cRed;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-height: 28px;
|
||||
min-width: 10em;
|
||||
padding: 0 2em;
|
||||
}
|
||||
|
||||
.number-input {
|
||||
max-width: 6em;
|
||||
}
|
||||
}
|
||||
.select-multiple {
|
||||
display: flex;
|
||||
.option-list {
|
||||
margin: 0;
|
||||
padding-left: .5em;
|
||||
}
|
||||
}
|
||||
.setting-list,
|
||||
.option-list{
|
||||
list-style-type: none;
|
||||
padding-left: 2em;
|
||||
li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.suboptions {
|
||||
margin-top: 0.3em
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -26,6 +26,11 @@
|
|||
{{ $t("nav.dms") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser" @click="toggleDrawer">
|
||||
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
|
||||
{{ $t("nav.interactions") }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li v-if="currentUser" @click="toggleDrawer">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Attachment from '../attachment/attachment.vue'
|
||||
import FavoriteButton from '../favorite_button/favorite_button.vue'
|
||||
import RetweetButton from '../retweet_button/retweet_button.vue'
|
||||
import DeleteButton from '../delete_button/delete_button.vue'
|
||||
import ExtraButtons from '../extra_buttons/extra_buttons.vue'
|
||||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||
import UserCard from '../user_card/user_card.vue'
|
||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||
|
@ -26,7 +26,8 @@ const Status = {
|
|||
'replies',
|
||||
'isPreview',
|
||||
'noHeading',
|
||||
'inlineExpanded'
|
||||
'inlineExpanded',
|
||||
'showPinned'
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
|
@ -37,6 +38,7 @@ const Status = {
|
|||
showPreview: false,
|
||||
showingTall: this.inConversation && this.focused,
|
||||
showingLongSubject: false,
|
||||
error: null,
|
||||
expandingSubject: typeof this.$store.state.config.collapseMessageWithSubject === 'undefined'
|
||||
? !this.$store.state.instance.collapseMessageWithSubject
|
||||
: !this.$store.state.config.collapseMessageWithSubject,
|
||||
|
@ -269,13 +271,19 @@ const Status = {
|
|||
this.statusFromGlobalRepository.rebloggedBy
|
||||
)
|
||||
return uniqBy(combinedUsers, 'id')
|
||||
},
|
||||
ownStatus () {
|
||||
return this.status.user.id === this.$store.state.users.currentUser.id
|
||||
},
|
||||
tags () {
|
||||
return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Attachment,
|
||||
FavoriteButton,
|
||||
RetweetButton,
|
||||
DeleteButton,
|
||||
ExtraButtons,
|
||||
PostStatusForm,
|
||||
UserCard,
|
||||
UserAvatar,
|
||||
|
@ -296,6 +304,12 @@ const Status = {
|
|||
return 'icon-globe'
|
||||
}
|
||||
},
|
||||
showError (error) {
|
||||
this.error = error
|
||||
},
|
||||
clearError () {
|
||||
this.error = undefined
|
||||
},
|
||||
linkClicked (event) {
|
||||
let { target } = event
|
||||
if (target.tagName === 'SPAN') {
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<div class="status-el" v-if="!hideStatus" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]">
|
||||
<div v-if="error" class="alert error">
|
||||
{{error}}
|
||||
<i class="button-icon icon-cancel" @click="clearError"></i>
|
||||
</div>
|
||||
<template v-if="muted && !isPreview">
|
||||
<div class="media status container muted">
|
||||
<small>
|
||||
|
@ -12,6 +16,10 @@
|
|||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="showPinned && statusoid.pinned" class="status-pin">
|
||||
<i class="fa icon-pin faint"></i>
|
||||
<span class="faint">{{$t('status.pinned')}}</span>
|
||||
</div>
|
||||
<div v-if="retweet && !noHeading && !inConversation" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info">
|
||||
<UserAvatar class="media-left" v-if="retweet" :betterShadow="betterShadow" :user="statusoid.user"/>
|
||||
<div class="media-body faint">
|
||||
|
@ -24,7 +32,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet && !inConversation }]" :style="[ userStyle ]" class="media status">
|
||||
<div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet && !inConversation }]" :style="[ userStyle ]" class="media status" :data-tags="tags">
|
||||
<div v-if="!noHeading" class="media-left">
|
||||
<router-link :to="userProfileLink" @click.stop.prevent.capture.native="toggleUserExpanded">
|
||||
<UserAvatar :compact="compact" :betterShadow="betterShadow" :user="status.user"/>
|
||||
|
@ -95,7 +103,7 @@
|
|||
v-if="preview"
|
||||
:isPreview="true"
|
||||
:statusoid="preview"
|
||||
:compact=true
|
||||
:compact="true"
|
||||
/>
|
||||
<div v-else class="status-preview status-preview-loading">
|
||||
<i class="icon-spin4 animate-spin"></i>
|
||||
|
@ -157,18 +165,18 @@
|
|||
</transition>
|
||||
|
||||
<div v-if="!noHeading && !isPreview" class='status-actions media-body'>
|
||||
<div v-if="loggedIn">
|
||||
<i class="button-icon icon-reply" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')" :class="{'icon-reply-active': replying}"></i>
|
||||
<div>
|
||||
<i class="button-icon icon-reply" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')" :class="{'button-icon-active': replying}" v-if="loggedIn"/>
|
||||
<i class="button-icon button-icon-disabled icon-reply" :title="$t('tool_tip.reply')" v-else />
|
||||
<span v-if="status.replies_count > 0">{{status.replies_count}}</span>
|
||||
</div>
|
||||
<retweet-button :visibility='status.visibility' :loggedIn='loggedIn' :status='status'></retweet-button>
|
||||
<favorite-button :loggedIn='loggedIn' :status='status'></favorite-button>
|
||||
<delete-button :status='status'></delete-button>
|
||||
<extra-buttons :status="status" @onError="showError" @onSuccess="clearError"></extra-buttons>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" v-if="replying">
|
||||
<div class="reply-left"/>
|
||||
<post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" :copy-message-scope="status.visibility" :subject="replySubject" v-on:posted="toggleReplying"/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -198,6 +206,13 @@ $status-margin: 0.75em;
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.status-pin {
|
||||
padding: $status-margin $status-margin 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.status-preview {
|
||||
position: absolute;
|
||||
max-width: 95%;
|
||||
|
@ -241,7 +256,6 @@ $status-margin: 0.75em;
|
|||
}
|
||||
|
||||
.status-el {
|
||||
hyphens: auto;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
|
@ -570,15 +584,13 @@ $status-margin: 0.75em;
|
|||
}
|
||||
}
|
||||
|
||||
.icon-reply:hover {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-reply.icon-reply-active {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
.button-icon.icon-reply {
|
||||
&:not(.button-icon-disabled):hover,
|
||||
&.button-icon-active {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.status:hover .animated.avatar {
|
||||
|
@ -618,11 +630,6 @@ a.unmute {
|
|||
margin-left: auto;
|
||||
}
|
||||
|
||||
.reply-left {
|
||||
flex: 0;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
.reply-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
|
|
@ -4,15 +4,18 @@ import './tab_switcher.scss'
|
|||
|
||||
export default Vue.component('tab-switcher', {
|
||||
name: 'TabSwitcher',
|
||||
props: ['renderOnlyFocused'],
|
||||
props: ['renderOnlyFocused', 'onSwitch'],
|
||||
data () {
|
||||
return {
|
||||
active: this.$slots.default.findIndex(_ => _.tag)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
activateTab (index) {
|
||||
activateTab (index, dataset) {
|
||||
return () => {
|
||||
if (typeof this.onSwitch === 'function') {
|
||||
this.onSwitch.call(null, index, this.$slots.default[index].elm.dataset)
|
||||
}
|
||||
this.active = index
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +40,11 @@ export default Vue.component('tab-switcher', {
|
|||
|
||||
return (
|
||||
<div class={ classesWrapper.join(' ')}>
|
||||
<button disabled={slot.data.attrs.disabled} onClick={this.activateTab(index)} class={ classesTab.join(' ') }>{slot.data.attrs.label}</button>
|
||||
<button
|
||||
disabled={slot.data.attrs.disabled}
|
||||
onClick={this.activateTab(index)}
|
||||
class={classesTab.join(' ')}>
|
||||
{slot.data.attrs.label}</button>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<router-link :to="userProfileLink(user)">
|
||||
<UserAvatar :betterShadow="betterShadow" :user="user"/>
|
||||
</router-link>
|
||||
<div class="name-and-screen-name">
|
||||
<div class="user-summary">
|
||||
<div class="top-line">
|
||||
<div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div>
|
||||
<div :title="user.name" class='user-name' v-else>{{user.name}}</div>
|
||||
|
@ -18,12 +18,12 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<router-link class='user-screen-name' :to="userProfileLink(user)">
|
||||
<span class="handle">@{{user.screen_name}}
|
||||
<span class="alert staff" v-if="!hideBio && !!visibleRole">{{visibleRole}}</span>
|
||||
</span><span v-if="user.locked"><i class="icon icon-lock"></i></span>
|
||||
<div class="bottom-line">
|
||||
<router-link class="user-screen-name" :to="userProfileLink(user)">@{{user.screen_name}}</router-link>
|
||||
<span class="alert staff" v-if="!hideBio && !!visibleRole">{{visibleRole}}</span>
|
||||
<span v-if="user.locked"><i class="icon icon-lock"></i></span>
|
||||
<span v-if="!hideUserStatsLocal && !hideBio" class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-meta">
|
||||
|
@ -232,7 +232,7 @@
|
|||
opacity: .8;
|
||||
}
|
||||
|
||||
.name-and-screen-name {
|
||||
.user-summary {
|
||||
display: block;
|
||||
margin-left: 0.6em;
|
||||
text-align: left;
|
||||
|
@ -249,6 +249,7 @@
|
|||
vertical-align: middle;
|
||||
object-fit: contain
|
||||
}
|
||||
|
||||
.top-line {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -269,15 +270,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
.user-screen-name {
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
display: inline-block;
|
||||
.bottom-line {
|
||||
display: flex;
|
||||
font-weight: light;
|
||||
font-size: 15px;
|
||||
padding-right: 0.1em;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.user-screen-name {
|
||||
min-width: 1px;
|
||||
flex: 0 1 auto;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
}
|
||||
|
||||
.dailyAvg {
|
||||
min-width: 1px;
|
||||
|
@ -288,15 +293,9 @@
|
|||
color: var(--text, $fallback--text);
|
||||
}
|
||||
|
||||
.handle {
|
||||
min-width: 1px;
|
||||
flex: 0 1 auto;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// TODO use proper colors
|
||||
.staff {
|
||||
flex: none;
|
||||
text-transform: capitalize;
|
||||
color: $fallback--text;
|
||||
color: var(--btnText, $fallback--text);
|
||||
|
|
|
@ -2,6 +2,7 @@ import get from 'lodash/get'
|
|||
import UserCard from '../user_card/user_card.vue'
|
||||
import FollowCard from '../follow_card/follow_card.vue'
|
||||
import Timeline from '../timeline/timeline.vue'
|
||||
import Conversation from '../conversation/conversation.vue'
|
||||
import ModerationTools from '../moderation_tools/moderation_tools.vue'
|
||||
import List from '../list/list.vue'
|
||||
import withLoadMore from '../../hocs/with_load_more/with_load_more'
|
||||
|
@ -95,6 +96,8 @@ const UserProfile = {
|
|||
if (this.isUs) {
|
||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId })
|
||||
}
|
||||
// Fetch all pinned statuses immediately
|
||||
this.$store.dispatch('fetchPinnedStatuses', userId)
|
||||
},
|
||||
cleanUp () {
|
||||
this.$store.dispatch('stopFetching', 'user')
|
||||
|
@ -128,7 +131,8 @@ const UserProfile = {
|
|||
FollowerList,
|
||||
FriendList,
|
||||
ModerationTools,
|
||||
FollowCard
|
||||
FollowCard,
|
||||
Conversation
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,16 +3,28 @@
|
|||
<div v-if="user" class="user-profile panel panel-default">
|
||||
<UserCard :user="user" :switcher="true" :selected="timeline.viewing" rounded="top"/>
|
||||
<tab-switcher :renderOnlyFocused="true" ref="tabSwitcher">
|
||||
<Timeline
|
||||
:label="$t('user_card.statuses')"
|
||||
:disabled="!user.statuses_count"
|
||||
:count="user.statuses_count"
|
||||
:embedded="true"
|
||||
:title="$t('user_profile.timeline_title')"
|
||||
:timeline="timeline"
|
||||
:timeline-name="'user'"
|
||||
:user-id="userId"
|
||||
/>
|
||||
<div :label="$t('user_card.statuses')" :disabled="!user.statuses_count">
|
||||
<div class="timeline">
|
||||
<template v-for="statusId in user.pinnedStatuseIds">
|
||||
<Conversation
|
||||
v-if="timeline.statusesObject[statusId]"
|
||||
class="status-fadein"
|
||||
:key="statusId"
|
||||
:statusoid="timeline.statusesObject[statusId]"
|
||||
:collapsable="true"
|
||||
:showPinned="true"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<Timeline
|
||||
:count="user.statuses_count"
|
||||
:embedded="true"
|
||||
:title="$t('user_profile.timeline_title')"
|
||||
:timeline="timeline"
|
||||
:timeline-name="'user'"
|
||||
:user-id="userId"
|
||||
/>
|
||||
</div>
|
||||
<div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
|
||||
<FriendList :userId="userId">
|
||||
<template slot="item" slot-scope="{item}">
|
||||
|
|
|
@ -251,6 +251,10 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.visibility-tray {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
padding: 5px;
|
||||
height: auto;
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
"chat": "Local Chat",
|
||||
"friend_requests": "Follow Requests",
|
||||
"mentions": "Mentions",
|
||||
"interactions": "Interactions",
|
||||
"dms": "Direct Messages",
|
||||
"public_tl": "Public Timeline",
|
||||
"timeline": "Timeline",
|
||||
|
@ -78,6 +79,11 @@
|
|||
"repeated_you": "repeated your status",
|
||||
"no_more_notifications": "No more notifications"
|
||||
},
|
||||
"interactions": {
|
||||
"favs_repeats": "Repeats and Favorites",
|
||||
"follows": "New follows",
|
||||
"load_older": "Load older interactions"
|
||||
},
|
||||
"post_status": {
|
||||
"new_status": "Post new status",
|
||||
"account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
|
||||
|
@ -402,6 +408,11 @@
|
|||
"status": {
|
||||
"favorites": "Favorites",
|
||||
"repeats": "Repeats",
|
||||
"delete": "Delete status",
|
||||
"pin": "Pin on profile",
|
||||
"unpin": "Unpin from profile",
|
||||
"pinned": "Pinned",
|
||||
"delete_confirm": "Do you really want to delete this status?",
|
||||
"reply_to": "Reply to",
|
||||
"replies_list": "Replies:"
|
||||
},
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"back": "Назад",
|
||||
"chat": "Локальный чат",
|
||||
"mentions": "Упоминания",
|
||||
"interactions": "Взаимодействия",
|
||||
"public_tl": "Публичная лента",
|
||||
"timeline": "Лента",
|
||||
"twkn": "Федеративная лента"
|
||||
|
@ -36,6 +37,11 @@
|
|||
"read": "Прочесть",
|
||||
"repeated_you": "повторил(а) ваш статус"
|
||||
},
|
||||
"interactions": {
|
||||
"favs_repeats": "Повторы и фавориты",
|
||||
"follows": "Новые подписки",
|
||||
"load_older": "Загрузить старые взаимодействия"
|
||||
},
|
||||
"post_status": {
|
||||
"account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков",
|
||||
"account_not_locked_warning_link": "залочен",
|
||||
|
|
|
@ -424,6 +424,10 @@ export const mutations = {
|
|||
newStatus.favoritedBy.push(user)
|
||||
}
|
||||
},
|
||||
setPinned (state, status) {
|
||||
const newStatus = state.allStatusesObject[status.id]
|
||||
newStatus.pinned = status.pinned
|
||||
},
|
||||
setRetweeted (state, { status, value }) {
|
||||
const newStatus = state.allStatusesObject[status.id]
|
||||
|
||||
|
@ -533,6 +537,18 @@ const statuses = {
|
|||
rootState.api.backendInteractor.unfavorite(status.id)
|
||||
.then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
|
||||
},
|
||||
fetchPinnedStatuses ({ rootState, dispatch }, userId) {
|
||||
rootState.api.backendInteractor.fetchPinnedStatuses(userId)
|
||||
.then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true }))
|
||||
},
|
||||
pinStatus ({ rootState, commit }, statusId) {
|
||||
return rootState.api.backendInteractor.pinOwnStatus(statusId)
|
||||
.then((status) => commit('setPinned', status))
|
||||
},
|
||||
unpinStatus ({ rootState, commit }, statusId) {
|
||||
rootState.api.backendInteractor.unpinOwnStatus(statusId)
|
||||
.then((status) => commit('setPinned', status))
|
||||
},
|
||||
retweet ({ rootState, commit }, status) {
|
||||
// Optimistic retweeting...
|
||||
commit('setRetweeted', { status, value: true })
|
||||
|
|
|
@ -165,6 +165,15 @@ export const mutations = {
|
|||
state.currentUser.muteIds.push(muteId)
|
||||
}
|
||||
},
|
||||
setPinned (state, status) {
|
||||
const user = state.usersObject[status.user.id]
|
||||
const index = user.pinnedStatuseIds.indexOf(status.id)
|
||||
if (status.pinned && index === -1) {
|
||||
user.pinnedStatuseIds.push(status.id)
|
||||
} else if (!status.pinned && index !== -1) {
|
||||
user.pinnedStatuseIds.splice(index, 1)
|
||||
}
|
||||
},
|
||||
setUserForStatus (state, status) {
|
||||
status.user = state.usersObject[status.user.id]
|
||||
},
|
||||
|
@ -318,13 +327,17 @@ const users = {
|
|||
store.commit('addNewUsers', users)
|
||||
store.commit('addNewUsers', retweetedUsers)
|
||||
|
||||
// Reconnect users to statuses
|
||||
each(statuses, (status) => {
|
||||
// Reconnect users to statuses
|
||||
store.commit('setUserForStatus', status)
|
||||
// Set pinned statuses to user
|
||||
store.commit('setPinned', status)
|
||||
})
|
||||
// Reconnect users to retweets
|
||||
each(compact(map(statuses, 'retweeted_status')), (status) => {
|
||||
// Reconnect users to retweets
|
||||
store.commit('setUserForStatus', status)
|
||||
// Set pinned retweets to user
|
||||
store.commit('setPinned', status)
|
||||
})
|
||||
},
|
||||
addNewNotifications (store, { notifications }) {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
/* eslint-env browser */
|
||||
const LOGIN_URL = '/api/account/verify_credentials.json'
|
||||
const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing'
|
||||
const MENTIONS_URL = '/api/statuses/mentions.json'
|
||||
const REGISTRATION_URL = '/api/account/register.json'
|
||||
const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
|
||||
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
||||
|
@ -14,9 +12,9 @@ const FOLLOW_REQUESTS_URL = '/api/pleroma/friend_requests'
|
|||
const APPROVE_USER_URL = '/api/pleroma/friendships/approve'
|
||||
const DENY_USER_URL = '/api/pleroma/friendships/deny'
|
||||
const TAG_USER_URL = '/api/pleroma/admin/users/tag'
|
||||
const PERMISSION_GROUP_URL = '/api/pleroma/admin/permission_group'
|
||||
const ACTIVATION_STATUS_URL = '/api/pleroma/admin/activation_status'
|
||||
const ADMIN_USER_URL = '/api/pleroma/admin/user'
|
||||
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
|
||||
const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenName}/activation_status`
|
||||
const ADMIN_USERS_URL = '/api/pleroma/admin/users'
|
||||
const SUGGESTIONS_URL = '/api/v1/suggestions'
|
||||
|
||||
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
|
||||
|
@ -51,6 +49,8 @@ const MASTODON_STATUS_FAVORITEDBY_URL = id => `/api/v1/statuses/${id}/favourited
|
|||
const MASTODON_STATUS_REBLOGGEDBY_URL = id => `/api/v1/statuses/${id}/reblogged_by`
|
||||
const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
|
||||
const MASTODON_REPORT_USER_URL = '/api/v1/reports'
|
||||
const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin`
|
||||
const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin`
|
||||
|
||||
import { each, map, concat, last } from 'lodash'
|
||||
import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
|
||||
|
@ -210,6 +210,16 @@ const unfollowUser = ({id, credentials}) => {
|
|||
}).then((data) => data.json())
|
||||
}
|
||||
|
||||
const pinOwnStatus = ({ id, credentials }) => {
|
||||
return promisedRequest({ url: MASTODON_PIN_OWN_STATUS(id), credentials, method: 'POST' })
|
||||
.then((data) => parseStatus(data))
|
||||
}
|
||||
|
||||
const unpinOwnStatus = ({ id, credentials }) => {
|
||||
return promisedRequest({ url: MASTODON_UNPIN_OWN_STATUS(id), credentials, method: 'POST' })
|
||||
.then((data) => parseStatus(data))
|
||||
}
|
||||
|
||||
const blockUser = ({id, credentials}) => {
|
||||
return fetch(MASTODON_BLOCK_USER_URL(id), {
|
||||
headers: authHeaders(credentials),
|
||||
|
@ -308,13 +318,6 @@ const fetchFollowers = ({id, maxId, sinceId, limit = 20, credentials}) => {
|
|||
.then((data) => data.map(parseUser))
|
||||
}
|
||||
|
||||
const fetchAllFollowing = ({username, credentials}) => {
|
||||
const url = `${ALL_FOLLOWING_URL}/${username}.json`
|
||||
return fetch(url, { headers: authHeaders(credentials) })
|
||||
.then((data) => data.json())
|
||||
.then((data) => data.map(parseUser))
|
||||
}
|
||||
|
||||
const fetchFollowRequests = ({credentials}) => {
|
||||
const url = FOLLOW_REQUESTS_URL
|
||||
return fetch(url, { headers: authHeaders(credentials) })
|
||||
|
@ -387,7 +390,7 @@ const untagUser = ({tag, credentials, ...options}) => {
|
|||
const addRight = ({right, credentials, ...user}) => {
|
||||
const screenName = user.screen_name
|
||||
|
||||
return fetch(`${PERMISSION_GROUP_URL}/${screenName}/${right}`, {
|
||||
return fetch(PERMISSION_GROUP_URL(screenName, right), {
|
||||
method: 'POST',
|
||||
headers: authHeaders(credentials),
|
||||
body: {}
|
||||
|
@ -397,7 +400,7 @@ const addRight = ({right, credentials, ...user}) => {
|
|||
const deleteRight = ({right, credentials, ...user}) => {
|
||||
const screenName = user.screen_name
|
||||
|
||||
return fetch(`${PERMISSION_GROUP_URL}/${screenName}/${right}`, {
|
||||
return fetch(PERMISSION_GROUP_URL(screenName, right), {
|
||||
method: 'DELETE',
|
||||
headers: authHeaders(credentials),
|
||||
body: {}
|
||||
|
@ -413,7 +416,7 @@ const setActivationStatus = ({status, credentials, ...user}) => {
|
|||
const headers = authHeaders(credentials)
|
||||
headers['Content-Type'] = 'application/json'
|
||||
|
||||
return fetch(`${ACTIVATION_STATUS_URL}/${screenName}.json`, {
|
||||
return fetch(ACTIVATION_STATUS_URL(screenName), {
|
||||
method: 'PUT',
|
||||
headers: headers,
|
||||
body: JSON.stringify(body)
|
||||
|
@ -424,7 +427,7 @@ const deleteUser = ({credentials, ...user}) => {
|
|||
const screenName = user.screen_name
|
||||
const headers = authHeaders(credentials)
|
||||
|
||||
return fetch(`${ADMIN_USER_URL}.json?nickname=${screenName}`, {
|
||||
return fetch(`${ADMIN_USERS_URL}?nickname=${screenName}`, {
|
||||
method: 'DELETE',
|
||||
headers: headers
|
||||
})
|
||||
|
@ -434,7 +437,6 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
|
|||
const timelineUrls = {
|
||||
public: MASTODON_PUBLIC_TIMELINE,
|
||||
friends: MASTODON_USER_HOME_TIMELINE_URL,
|
||||
mentions: MENTIONS_URL,
|
||||
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
|
||||
notifications: MASTODON_USER_NOTIFICATIONS_URL,
|
||||
'publicAndExternal': MASTODON_PUBLIC_TIMELINE,
|
||||
|
@ -488,6 +490,12 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
|
|||
.then((data) => data.map(isNotifications ? parseNotification : parseStatus))
|
||||
}
|
||||
|
||||
const fetchPinnedStatuses = ({ id, credentials }) => {
|
||||
const url = MASTODON_USER_TIMELINE_URL(id) + '?pinned=true'
|
||||
return promisedRequest({ url, credentials })
|
||||
.then((data) => data.map(parseStatus))
|
||||
}
|
||||
|
||||
const verifyCredentials = (user) => {
|
||||
return fetch(LOGIN_URL, {
|
||||
method: 'POST',
|
||||
|
@ -708,6 +716,7 @@ const reportUser = ({credentials, userId, statusIds, comment, forward}) => {
|
|||
const apiService = {
|
||||
verifyCredentials,
|
||||
fetchTimeline,
|
||||
fetchPinnedStatuses,
|
||||
fetchConversation,
|
||||
fetchStatus,
|
||||
fetchFriends,
|
||||
|
@ -715,6 +724,8 @@ const apiService = {
|
|||
fetchFollowers,
|
||||
followUser,
|
||||
unfollowUser,
|
||||
pinOwnStatus,
|
||||
unpinOwnStatus,
|
||||
blockUser,
|
||||
unblockUser,
|
||||
fetchUser,
|
||||
|
@ -726,7 +737,6 @@ const apiService = {
|
|||
postStatus,
|
||||
deleteStatus,
|
||||
uploadMedia,
|
||||
fetchAllFollowing,
|
||||
fetchMutes,
|
||||
muteUser,
|
||||
unmuteUser,
|
||||
|
|
|
@ -23,10 +23,6 @@ const backendInteractorService = (credentials) => {
|
|||
return apiService.fetchFollowers({id, maxId, sinceId, limit, credentials})
|
||||
}
|
||||
|
||||
const fetchAllFollowing = ({username}) => {
|
||||
return apiService.fetchAllFollowing({username, credentials})
|
||||
}
|
||||
|
||||
const fetchUser = ({id}) => {
|
||||
return apiService.fetchUser({id, credentials})
|
||||
}
|
||||
|
@ -98,6 +94,9 @@ const backendInteractorService = (credentials) => {
|
|||
const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials})
|
||||
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({credentials})
|
||||
const revokeOAuthToken = (id) => apiService.revokeOAuthToken({id, credentials})
|
||||
const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({credentials, id})
|
||||
const pinOwnStatus = (id) => apiService.pinOwnStatus({credentials, id})
|
||||
const unpinOwnStatus = (id) => apiService.unpinOwnStatus({credentials, id})
|
||||
|
||||
const getCaptcha = () => apiService.getCaptcha()
|
||||
const register = (params) => apiService.register(params)
|
||||
|
@ -134,7 +133,6 @@ const backendInteractorService = (credentials) => {
|
|||
unblockUser,
|
||||
fetchUser,
|
||||
fetchUserRelationship,
|
||||
fetchAllFollowing,
|
||||
verifyCredentials: apiService.verifyCredentials,
|
||||
startFetchingTimeline,
|
||||
startFetchingNotifications,
|
||||
|
@ -144,6 +142,9 @@ const backendInteractorService = (credentials) => {
|
|||
fetchBlocks,
|
||||
fetchOAuthTokens,
|
||||
revokeOAuthToken,
|
||||
fetchPinnedStatuses,
|
||||
pinOwnStatus,
|
||||
unpinOwnStatus,
|
||||
tagUser,
|
||||
untagUser,
|
||||
addRight,
|
||||
|
|
|
@ -131,6 +131,8 @@ export const parseUser = (data) => {
|
|||
output.statuses_count = data.statuses_count
|
||||
output.friendIds = []
|
||||
output.followerIds = []
|
||||
output.pinnedStatuseIds = []
|
||||
|
||||
if (data.pleroma) {
|
||||
output.follow_request_count = data.pleroma.follow_request_count
|
||||
}
|
||||
|
@ -141,6 +143,7 @@ export const parseUser = (data) => {
|
|||
}
|
||||
|
||||
output.tags = output.tags || []
|
||||
output.rights = output.rights || {}
|
||||
|
||||
return output
|
||||
}
|
||||
|
@ -168,7 +171,7 @@ export const addEmojis = (string, emojis) => {
|
|||
return emojis.reduce((acc, emoji) => {
|
||||
return acc.replace(
|
||||
new RegExp(`:${emoji.shortcode}:`, 'g'),
|
||||
`<img src='${emoji.url}' alt='${emoji.shortcode}' class='emoji' />`
|
||||
`<img src='${emoji.url}' alt='${emoji.shortcode}' title='${emoji.shortcode}' class='emoji' />`
|
||||
)
|
||||
}, string)
|
||||
}
|
||||
|
@ -189,6 +192,8 @@ export const parseStatus = (data) => {
|
|||
|
||||
output.statusnet_html = addEmojis(data.content, data.emojis)
|
||||
|
||||
output.tags = data.tags
|
||||
|
||||
if (data.pleroma) {
|
||||
const { pleroma } = data
|
||||
output.text = pleroma.content ? data.pleroma.content['text/plain'] : data.content
|
||||
|
@ -211,6 +216,7 @@ export const parseStatus = (data) => {
|
|||
|
||||
output.summary_html = addEmojis(data.spoiler_text, data.emojis)
|
||||
output.external_url = data.url
|
||||
output.pinned = data.pinned
|
||||
} else {
|
||||
output.favorited = data.favorited
|
||||
output.fave_num = data.fave_num
|
||||
|
|
|
@ -25,11 +25,13 @@ const sortById = (a, b) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const visibleNotificationsFromStore = store => {
|
||||
export const visibleNotificationsFromStore = (store, types) => {
|
||||
// map is just to clone the array since sort mutates it and it causes some issues
|
||||
let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById)
|
||||
sortedNotifications = sortBy(sortedNotifications, 'seen')
|
||||
return sortedNotifications.filter((notification) => visibleTypes(store).includes(notification.type))
|
||||
return sortedNotifications.filter(
|
||||
(notification) => (types || visibleTypes(store)).includes(notification.type)
|
||||
)
|
||||
}
|
||||
|
||||
export const unseenNotificationsFromStore = store =>
|
||||
|
|
0
static/font/LICENSE.txt
Normal file → Executable file
0
static/font/LICENSE.txt
Normal file → Executable file
0
static/font/README.txt
Normal file → Executable file
0
static/font/README.txt
Normal file → Executable file
22
static/font/config.json
Normal file → Executable file
22
static/font/config.json
Normal file → Executable file
|
@ -251,6 +251,26 @@
|
|||
"css": "wrench",
|
||||
"code": 59418,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "5b0772e9484a1a11646793a82edd622a",
|
||||
"css": "pin",
|
||||
"code": 59417,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "22411a88489225a018f68db737de3c77",
|
||||
"css": "ellipsis",
|
||||
"code": 61761,
|
||||
"src": "custom_icons",
|
||||
"selected": true,
|
||||
"svg": {
|
||||
"path": "M214 411V518Q214 540 199 556T161 571H54Q31 571 16 556T0 518V411Q0 388 16 373T54 357H161Q183 357 199 373T214 411ZM500 411V518Q500 540 484 556T446 571H339Q317 571 301 556T286 518V411Q286 388 301 373T339 357H446Q469 357 484 373T500 411ZM786 411V518Q786 540 770 556T732 571H625Q603 571 587 556T571 518V411Q571 388 587 373T625 357H732Q755 357 770 373T786 411Z",
|
||||
"width": 785.7
|
||||
},
|
||||
"search": [
|
||||
"ellipsis"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
3
static/font/css/fontello-codes.css
vendored
3
static/font/css/fontello-codes.css
vendored
|
@ -24,7 +24,7 @@
|
|||
.icon-adjust:before { content: '\e816'; } /* '' */
|
||||
.icon-edit:before { content: '\e817'; } /* '' */
|
||||
.icon-pencil:before { content: '\e818'; } /* '' */
|
||||
.icon-verified:before { content: '\e819'; } /* '' */
|
||||
.icon-pin:before { content: '\e819'; } /* '' */
|
||||
.icon-wrench:before { content: '\e81a'; } /* '' */
|
||||
.icon-spin3:before { content: '\e832'; } /* '' */
|
||||
.icon-spin4:before { content: '\e834'; } /* '' */
|
||||
|
@ -37,6 +37,7 @@
|
|||
.icon-plus-squared:before { content: '\f0fe'; } /* '' */
|
||||
.icon-reply:before { content: '\f112'; } /* '' */
|
||||
.icon-lock-open-alt:before { content: '\f13e'; } /* '' */
|
||||
.icon-ellipsis:before { content: '\f141'; } /* '' */
|
||||
.icon-play-circled:before { content: '\f144'; } /* '' */
|
||||
.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */
|
||||
.icon-binoculars:before { content: '\f1e5'; } /* '' */
|
||||
|
|
15
static/font/css/fontello-embedded.css
vendored
15
static/font/css/fontello-embedded.css
vendored
File diff suppressed because one or more lines are too long
3
static/font/css/fontello-ie7-codes.css
vendored
3
static/font/css/fontello-ie7-codes.css
vendored
|
@ -24,7 +24,7 @@
|
|||
.icon-adjust { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-pencil { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-verified { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
@ -37,6 +37,7 @@
|
|||
.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-ellipsis { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-play-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-thumbs-up-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
|
3
static/font/css/fontello-ie7.css
vendored
3
static/font/css/fontello-ie7.css
vendored
|
@ -35,7 +35,7 @@
|
|||
.icon-adjust { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-pencil { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-verified { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
@ -48,6 +48,7 @@
|
|||
.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-ellipsis { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-play-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-thumbs-up-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
|
|
17
static/font/css/fontello.css
vendored
17
static/font/css/fontello.css
vendored
|
@ -1,11 +1,11 @@
|
|||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('../font/fontello.eot?11878820');
|
||||
src: url('../font/fontello.eot?11878820#iefix') format('embedded-opentype'),
|
||||
url('../font/fontello.woff2?11878820') format('woff2'),
|
||||
url('../font/fontello.woff?11878820') format('woff'),
|
||||
url('../font/fontello.ttf?11878820') format('truetype'),
|
||||
url('../font/fontello.svg?11878820#fontello') format('svg');
|
||||
src: url('../font/fontello.eot?16609299');
|
||||
src: url('../font/fontello.eot?16609299#iefix') format('embedded-opentype'),
|
||||
url('../font/fontello.woff2?16609299') format('woff2'),
|
||||
url('../font/fontello.woff?16609299') format('woff'),
|
||||
url('../font/fontello.ttf?16609299') format('truetype'),
|
||||
url('../font/fontello.svg?16609299#fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
|||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('../font/fontello.svg?11878820#fontello') format('svg');
|
||||
src: url('../font/fontello.svg?16609299#fontello') format('svg');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -80,7 +80,7 @@
|
|||
.icon-adjust:before { content: '\e816'; } /* '' */
|
||||
.icon-edit:before { content: '\e817'; } /* '' */
|
||||
.icon-pencil:before { content: '\e818'; } /* '' */
|
||||
.icon-verified:before { content: '\e819'; } /* '' */
|
||||
.icon-pin:before { content: '\e819'; } /* '' */
|
||||
.icon-wrench:before { content: '\e81a'; } /* '' */
|
||||
.icon-spin3:before { content: '\e832'; } /* '' */
|
||||
.icon-spin4:before { content: '\e834'; } /* '' */
|
||||
|
@ -93,6 +93,7 @@
|
|||
.icon-plus-squared:before { content: '\f0fe'; } /* '' */
|
||||
.icon-reply:before { content: '\f112'; } /* '' */
|
||||
.icon-lock-open-alt:before { content: '\f13e'; } /* '' */
|
||||
.icon-ellipsis:before { content: '\f141'; } /* '' */
|
||||
.icon-play-circled:before { content: '\f144'; } /* '' */
|
||||
.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */
|
||||
.icon-binoculars:before { content: '\f1e5'; } /* '' */
|
||||
|
|
|
@ -229,11 +229,11 @@ body {
|
|||
}
|
||||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('./font/fontello.eot?60799712');
|
||||
src: url('./font/fontello.eot?60799712#iefix') format('embedded-opentype'),
|
||||
url('./font/fontello.woff?60799712') format('woff'),
|
||||
url('./font/fontello.ttf?60799712') format('truetype'),
|
||||
url('./font/fontello.svg?60799712#fontello') format('svg');
|
||||
src: url('./font/fontello.eot?79958594');
|
||||
src: url('./font/fontello.eot?79958594#iefix') format('embedded-opentype'),
|
||||
url('./font/fontello.woff?79958594') format('woff'),
|
||||
url('./font/fontello.ttf?79958594') format('truetype'),
|
||||
url('./font/fontello.svg?79958594#fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -335,7 +335,7 @@ body {
|
|||
</div>
|
||||
<div class="row">
|
||||
<div class="the-icons span3" title="Code: 0xe818"><i class="demo-icon icon-pencil"></i> <span class="i-name">icon-pencil</span><span class="i-code">0xe818</span></div>
|
||||
<div class="the-icons span3" title="Code: 0xe819"><i class="demo-icon icon-verified"></i> <span class="i-name">icon-verified</span><span class="i-code">0xe819</span></div>
|
||||
<div class="the-icons span3" title="Code: 0xe819"><i class="demo-icon icon-pin"></i> <span class="i-name">icon-pin</span><span class="i-code">0xe819</span></div>
|
||||
<div class="the-icons span3" title="Code: 0xe81a"><i class="demo-icon icon-wrench"></i> <span class="i-name">icon-wrench</span><span class="i-code">0xe81a</span></div>
|
||||
<div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin"></i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div>
|
||||
</div>
|
||||
|
@ -354,10 +354,11 @@ body {
|
|||
<div class="row">
|
||||
<div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply"></i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div>
|
||||
<div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt"></i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div>
|
||||
<div class="the-icons span3" title="Code: 0xf141"><i class="demo-icon icon-ellipsis"></i> <span class="i-name">icon-ellipsis</span><span class="i-code">0xf141</span></div>
|
||||
<div class="the-icons span3" title="Code: 0xf144"><i class="demo-icon icon-play-circled"></i> <span class="i-name">icon-play-circled</span><span class="i-code">0xf144</span></div>
|
||||
<div class="the-icons span3" title="Code: 0xf164"><i class="demo-icon icon-thumbs-up-alt"></i> <span class="i-name">icon-thumbs-up-alt</span><span class="i-code">0xf164</span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="the-icons span3" title="Code: 0xf164"><i class="demo-icon icon-thumbs-up-alt"></i> <span class="i-name">icon-thumbs-up-alt</span><span class="i-code">0xf164</span></div>
|
||||
<div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars"></i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div>
|
||||
<div class="the-icons span3" title="Code: 0xf234"><i class="demo-icon icon-user-plus"></i> <span class="i-name">icon-user-plus</span><span class="i-code">0xf234</span></div>
|
||||
</div>
|
||||
|
|
Binary file not shown.
|
@ -56,7 +56,7 @@
|
|||
|
||||
<glyph glyph-name="pencil" unicode="" d="M203 0l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="verified" unicode="" d="M926 453l-19 13c-21 14-30 41-23 65l6 22c10 34-13 69-48 75l-23 4c-25 4-45 23-49 48l-4 23c-6 35-41 57-75 47l-22-7c-24-7-51 2-65 22l-14 20c-21 29-62 33-88 9l-17-16c-19-17-46-21-69-8l-20 11c-31 17-70 3-84-30l-9-22c-9-24-33-39-58-37l-23 1c-36 2-65-28-62-63l2-23c2-25-13-49-36-59l-21-9c-33-14-46-53-29-84l12-20c13-22 10-50-7-69l-15-17c-24-27-19-68 11-88l19-13c21-14 30-41 23-65l-9-23c-10-34 13-69 48-75l23-4c25-4 45-23 49-48l4-23c6-35 41-57 75-47l22 7c24 7 51-2 65-22l14-19c21-29 62-33 88-9l17 16c19 17 46 21 69 8l20-11c31-17 70-3 84 30l9 22c9 24 33 39 58 37l23-1c36-2 65 28 62 63l-1 23c-2 25 13 49 36 59l21 9c33 14 46 53 29 84l-12 20c-13 22-10 50 7 69l15 17c25 26 20 68-9 88z m-399-189l-82-81-81 82-78 79 82 81 78-79 187 186 81-82-187-186z" horiz-adv-x="1000" />
|
||||
<glyph glyph-name="pin" unicode="" d="M268 375v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" />
|
||||
|
||||
<glyph glyph-name="wrench" unicode="" d="M214 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
|
||||
|
||||
|
@ -82,6 +82,8 @@
|
|||
|
||||
<glyph glyph-name="lock-open-alt" unicode="" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
|
||||
|
||||
<glyph glyph-name="ellipsis" unicode="" d="M214 446v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-15 38v107q0 23 15 38t38 16h107q23 0 38-16t16-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-16 38v107q0 23 16 38t38 16h107q23 0 38-16t16-38z" horiz-adv-x="785.7" />
|
||||
|
||||
<glyph glyph-name="play-circled" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m214-460q18 10 18 31t-18 31l-304 178q-17 11-35 1-18-11-18-31v-358q0-20 18-31 9-4 17-4 10 0 18 5z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="thumbs-up-alt" unicode="" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" />
|
||||
|
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -322,9 +322,9 @@ describe('API Entities normalizer', () => {
|
|||
|
||||
describe('MastoAPI emoji adder', () => {
|
||||
const emojis = makeMockEmojiMasto()
|
||||
const imageHtml = '<img src="https://example.com/image.png" alt="image" class="emoji" />'
|
||||
const imageHtml = '<img src="https://example.com/image.png" alt="image" title="image" class="emoji" />'
|
||||
.replace(/"/g, '\'')
|
||||
const thinkHtml = '<img src="https://example.com/think.png" alt="thinking" class="emoji" />'
|
||||
const thinkHtml = '<img src="https://example.com/think.png" alt="thinking" title="thinking" class="emoji" />'
|
||||
.replace(/"/g, '\'')
|
||||
|
||||
it('correctly replaces shortcodes in supplied string', () => {
|
||||
|
|
Loading…
Add table
Reference in a new issue