Page MenuHomePhorge

No OneTemporary

Size
26 KB
Referenced Files
None
Subscribers
None
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd4a28360f..a1506eb53b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,195 +1,196 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- Mouseover titles for emojis in reaction picker
- Support to input emoji into the search box in reaction picker
- Added some missing unicode emoji
### Fixed
- Fixed the occasional bug where screen would scroll 1px when typing into a reply form
- Fixed timeline errors locking timelines
- Fixed missing highlighted border in expanded conversations
- Fixed custom emoji not working in profile field names
- Fixed pinned statuses not appearing in user profiles
- Fixed some elements not being keyboard navigation friendly
- Fixed your latest chat messages disappearing when closing chat view and opening it again during the same session
+- Fixed custom emoji not showing in poll options before voting
### Changed
- Errors when fetching are now shown with popup errors instead of "Error fetching updates" in panel headers
- Made reply/fav/repeat etc buttons easier to hit
## [2.2.1] - 2020-11-11
### Fixed
- Fixed regression in react popup alignment and overflowing
## [2.2.0] - 2020-11-06
### Added
- New option to optimize timeline rendering to make the site more responsive (enabled by default)
- New instance option `logoLeft` to move logo to the left side in desktop nav bar
- Import/export a muted users
- Proper handling of deletes when using websocket streaming
- Added optimistic chat message sending, so you can start writing next message before the previous one has been sent
- Added a small red badge to the favicon when there's unread notifications
- Added the NSFW alert to link previews
### Fixed
- Fixed clicking NSFW hider through status popover
- Fixed chat-view back button being hard to click
- Fixed fresh chat notifications being cleared immediately while leaving the chat view and not having time to actually see the messages
- Fixed multiple regressions in CSS styles
- Fixed multiple issues with input fields when using CJK font as default
- Fixed search field in navbar infringing into logo in some cases
- Fixed not being able to load the chat history in vertical screens when the message list doesn't take the full height of the scrollable container on the first fetch.
### Changed
- Clicking immediately when timeline shifts is now blocked to prevent misclicks
- Icons changed from fontello (FontAwesome 4 + others) to FontAwesome 5 due to problems with fontello.
- Some icons changed for better accessibility (lock, globe)
- Logo is now clickable
- Changed default logo to SVG version
## [2.1.2] - 2020-09-17
### Fixed
- Fixed chats list not updating its order when new messages come in
- Fixed chat messages sometimes getting lost when you receive a message at the same time
## [2.1.1] - 2020-09-08
### Changed
- Polls will be hidden with status content if "Collapse posts with subjects" is enabled and the post is collapsed.
### Fixed
- Network fetches don't pile up anymore but wait for previous ones to finish to reduce throttling.
- Autocomplete won't stop at the second @, so it'll still work with "@lain@l" and not start over.
- Fixed weird autocomplete behavior when you write ":custom_emoji: ?"
## [2.1.0] - 2020-08-28
### Added
- Autocomplete domains from list of known instances
- 'Bot' settings option and badge
- Added profile meta data fields that can be set in profile settings
- Added option to reset avatar/banner in profile settings
- Descriptions can be set on uploaded files before posting
- Added status preview option to preview your statuses before posting
- When a post is a reply to an unavailable post, the 'Reply to'-text has a strike-through style
- Added ability to see all favoriting or repeating users when hovering the number on highlighted statuses
- Bookmarks
### Changed
- Change heart to thumbs up in reaction picker
- Close the media modal on navigation events
- Add colons to the emoji alt text, to make them copyable
- Add better visual indication for drag-and-drop for files
- When disabling attachments, the placeholder links now show an icon and the description instead of just IMAGE or VIDEO etc
- Remove unnecessary options for 'automatic loading when loading older' and 'reply previews'
- Greentext now has separate color slot for it
- Removed the use of with_move parameters when fetching notifications
- Push notifications now are the same as normal notfication, and are localized.
- Updated Notification Settings to match new BE API
### Fixed
- Custom Emoji will display in poll options now.
- Status ellipsis menu closes properly when selecting certain options
- Cropped images look correct in Chrome
- Newlines in the muted words settings work again
- Clicking on non-latin hashtags won't open a new window
- Uploading and drag-dropping multiple files works correctly now.
- Subject field now appears disabled when posting
- Fix status ellipsis menu being cut off in notifications column
- Fixed autocomplete sometimes not returning the right user when there's already some results
- Videos and audio and misc files show description as alt/title properly now
- Clicking on non-image/video files no longer opens an empty modal
- Audio files can now be played back in the frontend with hidden attachments
- Videos are not cropped awkwardly in the uploads section anymore
- Reply filtering options in Settings -> Filtering now work again using filtering on server
- Don't show just blank-screen when cookies are disabled
- Add status idempotency to prevent accidental double posting when posting returns an error
- Weird bug related to post being sent seemingly after pasting with keyboard (hopefully)
- Multiple issues with muted statuses/notifications
## [2.0.5] - 2020-05-12
### Added
- Added private notifications option for push notifications
- 'Copy link' button for statuses (in the ellipsis menu)
### Changed
- Registration page no longer requires email if the server is configured not to require it
### Fixed
- Status ellipsis menu closes properly when selecting certain options
## [2.0.3] - 2020-05-02
### Fixed
- Show more/less works correctly with auto-collapsed subjects and long posts
- RTL characters won't look messed up in notifications
### Changed
- Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach:
### Added
- Follow request notification support
## [2.0.2] - 2020-04-08
### Fixed
- Favorite/Repeat avatars not showing up on private instances/non-public posts
- Autocorrect getting triggered in the captcha field
- Overflow on long domains in follow/move notifications
### Changed
- Polish translation updated
## [2.0.0] - 2020-02-28
### Added
- Tons of color slots including ones for hover/pressed/toggled buttons
- Experimental `--variable[,mod]` syntax support for color slots in themes. the `mod` makes color brighter/darker depending on background color (makes darker color brighter/darker depending on background color)
- Paper theme by Shpuld
- Icons in nav panel
- Private mode support
- Support for 'Move' type notifications
- Pleroma AMOLED dark theme
- User level domain mutes, under User Settings -> Mutes
- Emoji reactions for statuses
- MRF keyword policy disclosure
### Changed
- Updated Pleroma default themes
- theme engine update to 3 (themes v2.1 introduction)
- massive internal changes in theme engine - slowly away from "generate things separately with spaghetti code" towards "feed all data into single 'generateTheme' function and declare slot inheritance and all in a separate file"
- Breezy theme updates to make it closer to actual Breeze in some aspects
- when using `--variable` in shadows it no longer uses the actual CSS3 variable, instead it generates color from other slots
- theme doesn't get saved to local storage when opening FE anonymously
- Captcha now resets on failed registrations
- Notifications column now cleans itself up to optimize performance when tab is left open for a long time
- 403 messaging
### Fixed
- Fixed loader-spinner not disappearing when a status preview fails to load
- anon viewers won't get theme data saved to local storage, so admin changing default theme will have an effect for users coming back to instance.
- Single notifications left unread when hitting read on another device/tab
- Registration fixed
- Deactivation of remote accounts from frontend
- Fixed NSFW unhiding not working with videos when using one-click unhiding/displaying
- Improved performance of anything that uses popovers (most notably statuses)
## [1.1.7 and earlier] - 2019-12-14
### Added
- Ability to hide/show repeats from user
- User profile button clutter organized into a menu
- Emoji picker
- Started changelog anew
- Ability to change user's email
- About page
- Added remote user redirect
### Changed
- changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes
### Fixed
- improved hotkey behavior on autocomplete popup
diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue
index 264a5f03ea..42819c19fa 100644
--- a/src/components/poll/poll.vue
+++ b/src/components/poll/poll.vue
@@ -1,138 +1,139 @@
<template>
<div
class="poll"
:class="containerClass"
>
<div
v-for="(option, index) in options"
:key="index"
class="poll-option"
>
<div
v-if="showResults"
:title="resultTitle(option)"
class="option-result"
>
<div class="option-result-label">
<span class="result-percentage">
{{ percentageForOption(option.votes_count) }}%
</span>
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="option.title_html" />
</div>
<div
class="result-fill"
:style="{ 'width': `${percentageForOption(option.votes_count)}%` }"
/>
</div>
<div
v-else
@click="activateOption(index)"
>
<input
v-if="poll.multiple"
type="checkbox"
:disabled="loading"
:value="index"
>
<input
v-else
type="radio"
:disabled="loading"
:value="index"
>
<label class="option-vote">
- <div>{{ option.title }}</div>
+ <!-- eslint-disable-next-line vue/no-v-html -->
+ <div v-html="option.title_html" />
</label>
</div>
</div>
<div class="footer faint">
<button
v-if="!showResults"
class="btn button-default poll-vote-button"
type="button"
:disabled="isDisabled"
@click="vote"
>
{{ $t('polls.vote') }}
</button>
<div class="total">
{{ totalVotesCount }} {{ $t("polls.votes") }}&nbsp;·&nbsp;
</div>
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
<Timeago
:time="expiresAt"
:auto-update="60"
:now-threshold="0"
/>
</i18n>
</div>
</div>
</template>
<script src="./poll.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.poll {
.votes {
display: flex;
flex-direction: column;
margin: 0 0 0.5em;
}
.poll-option {
margin: 0.75em 0.5em;
}
.option-result {
height: 100%;
display: flex;
flex-direction: row;
position: relative;
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
.option-result-label {
display: flex;
align-items: center;
padding: 0.1em 0.25em;
z-index: 1;
word-break: break-word;
}
.result-percentage {
width: 3.5em;
flex-shrink: 0;
}
.result-fill {
height: 100%;
position: absolute;
color: $fallback--text;
color: var(--pollText, $fallback--text);
background-color: $fallback--lightBg;
background-color: var(--poll, $fallback--lightBg);
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
top: 0;
left: 0;
transition: width 0.5s;
}
.option-vote {
display: flex;
align-items: center;
}
input {
width: 3.5em;
}
.footer {
display: flex;
align-items: center;
}
&.loading * {
cursor: progress;
}
.poll-vote-button {
padding: 0 0.5em;
margin-right: 0.5em;
}
}
</style>
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index e3a52489bb..b1e68df5a3 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -1,443 +1,443 @@
import escape from 'escape-html'
import parseLinkHeader from 'parse-link-header'
import { isStatusNotification } from '../notification_utils/notification_utils.js'
/** NOTICE! **
* Do not initialize UI-generated data here.
* It will override existing data.
*
* i.e. user.pinnedStatusIds was set to [] here
* UI code would update it with data but upon next user fetch
* it would be reverted back to []
*/
const qvitterStatusType = (status) => {
if (status.is_post_verb) {
return 'status'
}
if (status.retweeted_status) {
return 'retweet'
}
if ((typeof status.uri === 'string' && status.uri.match(/(fave|objectType=Favourite)/)) ||
(typeof status.text === 'string' && status.text.match(/favorited/))) {
return 'favorite'
}
if (status.text.match(/deleted notice {{tag/) || status.qvitter_delete_notice) {
return 'deletion'
}
if (status.text.match(/started following/) || status.activity_type === 'follow') {
return 'follow'
}
return 'unknown'
}
export const parseUser = (data) => {
const output = {}
const masto = data.hasOwnProperty('acct')
// case for users in "mentions" property for statuses in MastoAPI
const mastoShort = masto && !data.hasOwnProperty('avatar')
output.id = String(data.id)
if (masto) {
output.screen_name = data.acct
output.statusnet_profile_url = data.url
// There's nothing else to get
if (mastoShort) {
return output
}
output.name = data.display_name
output.name_html = addEmojis(escape(data.display_name), data.emojis)
output.description = data.note
output.description_html = addEmojis(data.note, data.emojis)
output.fields = data.fields
output.fields_html = data.fields.map(field => {
return {
name: addEmojis(escape(field.name), data.emojis),
value: addEmojis(field.value, data.emojis)
}
})
output.fields_text = data.fields.map(field => {
return {
name: unescape(field.name.replace(/<[^>]*>/g, '')),
value: unescape(field.value.replace(/<[^>]*>/g, ''))
}
})
// Utilize avatar_static for gif avatars?
output.profile_image_url = data.avatar
output.profile_image_url_original = data.avatar
// Same, utilize header_static?
output.cover_photo = data.header
output.friends_count = data.following_count
output.bot = data.bot
if (data.pleroma) {
const relationship = data.pleroma.relationship
output.background_image = data.pleroma.background_image
output.favicon = data.pleroma.favicon
output.token = data.pleroma.chat_token
if (relationship) {
output.relationship = relationship
}
output.allow_following_move = data.pleroma.allow_following_move
output.hide_follows = data.pleroma.hide_follows
output.hide_followers = data.pleroma.hide_followers
output.hide_follows_count = data.pleroma.hide_follows_count
output.hide_followers_count = data.pleroma.hide_followers_count
output.rights = {
moderator: data.pleroma.is_moderator,
admin: data.pleroma.is_admin
}
// TODO: Clean up in UI? This is duplication from what BE does for qvitterapi
if (output.rights.admin) {
output.role = 'admin'
} else if (output.rights.moderator) {
output.role = 'moderator'
} else {
output.role = 'member'
}
}
if (data.source) {
output.description = data.source.note
output.default_scope = data.source.privacy
output.fields = data.source.fields
if (data.source.pleroma) {
output.no_rich_text = data.source.pleroma.no_rich_text
output.show_role = data.source.pleroma.show_role
output.discoverable = data.source.pleroma.discoverable
}
}
// TODO: handle is_local
output.is_local = !output.screen_name.includes('@')
} else {
output.screen_name = data.screen_name
output.name = data.name
output.name_html = data.name_html
output.description = data.description
output.description_html = data.description_html
output.profile_image_url = data.profile_image_url
output.profile_image_url_original = data.profile_image_url_original
output.cover_photo = data.cover_photo
output.friends_count = data.friends_count
// output.bot = ??? missing
output.statusnet_profile_url = data.statusnet_profile_url
output.is_local = data.is_local
output.role = data.role
output.show_role = data.show_role
if (data.rights) {
output.rights = {
moderator: data.rights.delete_others_notice,
admin: data.rights.admin
}
}
output.no_rich_text = data.no_rich_text
output.default_scope = data.default_scope
output.hide_follows = data.hide_follows
output.hide_followers = data.hide_followers
output.hide_follows_count = data.hide_follows_count
output.hide_followers_count = data.hide_followers_count
output.background_image = data.background_image
// Websocket token
output.token = data.token
// Convert relationsip data to expected format
output.relationship = {
muting: data.muted,
blocking: data.statusnet_blocking,
followed_by: data.follows_you,
following: data.following
}
}
output.created_at = new Date(data.created_at)
output.locked = data.locked
output.followers_count = data.followers_count
output.statuses_count = data.statuses_count
if (data.pleroma) {
output.follow_request_count = data.pleroma.follow_request_count
output.tags = data.pleroma.tags
output.deactivated = data.pleroma.deactivated
output.notification_settings = data.pleroma.notification_settings
output.unread_chat_count = data.pleroma.unread_chat_count
}
output.tags = output.tags || []
output.rights = output.rights || {}
output.notification_settings = output.notification_settings || {}
return output
}
export const parseAttachment = (data) => {
const output = {}
const masto = !data.hasOwnProperty('oembed')
if (masto) {
// Not exactly same...
output.mimetype = data.pleroma ? data.pleroma.mime_type : data.type
output.meta = data.meta // not present in BE yet
output.id = data.id
} else {
output.mimetype = data.mimetype
// output.meta = ??? missing
}
output.url = data.url
output.large_thumb_url = data.preview_url
output.description = data.description
return output
}
export const addEmojis = (string, emojis) => {
const matchOperatorsRegex = /[|\\{}()[\]^$+*?.-]/g
return emojis.reduce((acc, emoji) => {
const regexSafeShortCode = emoji.shortcode.replace(matchOperatorsRegex, '\\$&')
return acc.replace(
new RegExp(`:${regexSafeShortCode}:`, 'g'),
`<img src='${emoji.url}' alt=':${emoji.shortcode}:' title=':${emoji.shortcode}:' class='emoji' />`
)
}, string)
}
export const parseStatus = (data) => {
const output = {}
const masto = data.hasOwnProperty('account')
if (masto) {
output.favorited = data.favourited
output.fave_num = data.favourites_count
output.repeated = data.reblogged
output.repeat_num = data.reblogs_count
output.bookmarked = data.bookmarked
output.type = data.reblog ? 'retweet' : 'status'
output.nsfw = data.sensitive
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
output.summary = pleroma.spoiler_text ? data.pleroma.spoiler_text['text/plain'] : data.spoiler_text
output.statusnet_conversation_id = data.pleroma.conversation_id
output.is_local = pleroma.local
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
output.thread_muted = pleroma.thread_muted
output.emoji_reactions = pleroma.emoji_reactions
output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible
} else {
output.text = data.content
output.summary = data.spoiler_text
}
output.in_reply_to_status_id = data.in_reply_to_id
output.in_reply_to_user_id = data.in_reply_to_account_id
output.replies_count = data.replies_count
if (output.type === 'retweet') {
output.retweeted_status = parseStatus(data.reblog)
}
output.summary_html = addEmojis(escape(data.spoiler_text), data.emojis)
output.external_url = data.url
output.poll = data.poll
if (output.poll) {
output.poll.options = (output.poll.options || []).map(field => ({
...field,
- title_html: addEmojis(field.title, data.emojis)
+ title_html: addEmojis(escape(field.title), data.emojis)
}))
}
output.pinned = data.pinned
output.muted = data.muted
} else {
output.favorited = data.favorited
output.fave_num = data.fave_num
output.repeated = data.repeated
output.repeat_num = data.repeat_num
// catchall, temporary
// Object.assign(output, data)
output.type = qvitterStatusType(data)
if (data.nsfw === undefined) {
output.nsfw = isNsfw(data)
if (data.retweeted_status) {
output.nsfw = data.retweeted_status.nsfw
}
} else {
output.nsfw = data.nsfw
}
output.statusnet_html = data.statusnet_html
output.text = data.text
output.in_reply_to_status_id = data.in_reply_to_status_id
output.in_reply_to_user_id = data.in_reply_to_user_id
output.in_reply_to_screen_name = data.in_reply_to_screen_name
output.statusnet_conversation_id = data.statusnet_conversation_id
if (output.type === 'retweet') {
output.retweeted_status = parseStatus(data.retweeted_status)
}
output.summary = data.summary
output.summary_html = data.summary_html
output.external_url = data.external_url
output.is_local = data.is_local
}
output.id = String(data.id)
output.visibility = data.visibility
output.card = data.card
output.created_at = new Date(data.created_at)
// Converting to string, the right way.
output.in_reply_to_status_id = output.in_reply_to_status_id
? String(output.in_reply_to_status_id)
: null
output.in_reply_to_user_id = output.in_reply_to_user_id
? String(output.in_reply_to_user_id)
: null
output.user = parseUser(masto ? data.account : data.user)
output.attentions = ((masto ? data.mentions : data.attentions) || []).map(parseUser)
output.attachments = ((masto ? data.media_attachments : data.attachments) || [])
.map(parseAttachment)
const retweetedStatus = masto ? data.reblog : data.retweeted_status
if (retweetedStatus) {
output.retweeted_status = parseStatus(retweetedStatus)
}
output.favoritedBy = []
output.rebloggedBy = []
return output
}
export const parseNotification = (data) => {
const mastoDict = {
'favourite': 'like',
'reblog': 'repeat'
}
const masto = !data.hasOwnProperty('ntype')
const output = {}
if (masto) {
output.type = mastoDict[data.type] || data.type
output.seen = data.pleroma.is_seen
output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null
output.action = output.status // TODO: Refactor, this is unneeded
output.target = output.type !== 'move'
? null
: parseUser(data.target)
output.from_profile = parseUser(data.account)
output.emoji = data.emoji
} else {
const parsedNotice = parseStatus(data.notice)
output.type = data.ntype
output.seen = Boolean(data.is_seen)
output.status = output.type === 'like'
? parseStatus(data.notice.favorited_status)
: parsedNotice
output.action = parsedNotice
output.from_profile = output.type === 'pleroma:chat_mention' ? parseUser(data.account) : parseUser(data.from_profile)
}
output.created_at = new Date(data.created_at)
output.id = parseInt(data.id)
return output
}
const isNsfw = (status) => {
const nsfwRegex = /#nsfw/i
return (status.tags || []).includes('nsfw') || !!(status.text || '').match(nsfwRegex)
}
export const parseLinkHeaderPagination = (linkHeader, opts = {}) => {
const flakeId = opts.flakeId
const parsedLinkHeader = parseLinkHeader(linkHeader)
if (!parsedLinkHeader) return
const maxId = parsedLinkHeader.next.max_id
const minId = parsedLinkHeader.prev.min_id
return {
maxId: flakeId ? maxId : parseInt(maxId, 10),
minId: flakeId ? minId : parseInt(minId, 10)
}
}
export const parseChat = (chat) => {
const output = {}
output.id = chat.id
output.account = parseUser(chat.account)
output.unread = chat.unread
output.lastMessage = parseChatMessage(chat.last_message)
output.updated_at = new Date(chat.updated_at)
return output
}
export const parseChatMessage = (message) => {
if (!message) { return }
if (message.isNormalized) { return message }
const output = message
output.id = message.id
output.created_at = new Date(message.created_at)
output.chat_id = message.chat_id
if (message.content) {
output.content = addEmojis(message.content, message.emojis)
} else {
output.content = ''
}
if (message.attachment) {
output.attachments = [parseAttachment(message.attachment)]
} else {
output.attachments = []
}
output.pending = !!message.pending
output.error = false
output.idempotency_key = message.idempotency_key
output.isNormalized = true
return output
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Aug 30, 6:15 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1737823
Default Alt Text
(26 KB)

Event Timeline