Merge remote-tracking branch 'origin/develop' into shigusegubu

* origin/develop: (23 commits)
  change icon to a megaphone
  change side drawer to use shoutbox name
  update changelog
  Fix chat messages being missed when the streaming is disabled and the messages are sent by both participants simultaneously
  Fix the chat list order and last message timestamp updates
  change a eslint disable to nextline only
  fix vue warnings and errors
  Translated using Weblate (Basque)
  Translated using Weblate (Spanish)
  Translated using Weblate (Spanish)
  Translated using Weblate (Spanish)
  Do not show desktop notifications for your own chat messages
  Add hacky functionality to open specific settings tabs
  Password reset no longer informs user of errors or account existence
  add changelog entry
  hide poll when subject collapsed, but show poll icon
  update changelog for autocomplete fixes
  update changelog with 2.1.0, fix Add -> Added in older releases
  Rewrite word split imperatively for control
  wip start
  ...
This commit is contained in:
Henry Jameson 2020-09-17 23:24:03 +03:00
commit 8e76a1dc53
39 changed files with 447 additions and 172 deletions

View file

@ -2,21 +2,26 @@
All notable changes to this project will be documented in this file. 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/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Changed ## [Unreleased patch]
- 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 ### Fixed
- Weird bug related to post being sent seemingly after pasting with keyboard (hopefully) - Fixed chats list not updating its order when new messages come in
- Multiple issues with muted statuses/notifications - Fixed chat messages sometimes getting lost when you receive a message at the same time
## [Unreleased patch]
### Add ## [2.1.1] - 2020-09-08
- Added private notifications option for push notifications ### Changed
- 'Copy link' button for statuses (in the ellipsis menu) - Polls will be hidden with status content if "Collapse posts with subjects" is enabled and the post is collapsed.
### Fixed
- 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 - Autocomplete domains from list of known instances
- 'Bot' settings option and badge - 'Bot' settings option and badge
- Added profile meta data fields that can be set in profile settings - Added profile meta data fields that can be set in profile settings
@ -25,15 +30,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added status preview option to preview your statuses 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 - 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 - Added ability to see all favoriting or repeating users when hovering the number on highlighted statuses
- Bookmarks
### Changed ### Changed
- Registration page no longer requires email if the server is configured not to require it
- Change heart to thumbs up in reaction picker - Change heart to thumbs up in reaction picker
- Close the media modal on navigation events - Close the media modal on navigation events
- Add colons to the emoji alt text, to make them copyable - Add colons to the emoji alt text, to make them copyable
- Add better visual indication for drag-and-drop for files - 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 - 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' - 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 ### Fixed
- Custom Emoji will display in poll options now. - Custom Emoji will display in poll options now.
@ -52,6 +61,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Reply filtering options in Settings -> Filtering now work again using filtering on server - Reply filtering options in Settings -> Filtering now work again using filtering on server
- Don't show just blank-screen when cookies are disabled - Don't show just blank-screen when cookies are disabled
- Add status idempotency to prevent accidental double posting when posting returns an error - 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 ## [2.0.3] - 2020-05-02
### Fixed ### Fixed
@ -61,7 +83,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed ### Changed
- Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach: - Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach:
### Add ### Added
- Follow request notification support - Follow request notification support
## [2.0.2] - 2020-04-08 ## [2.0.2] - 2020-04-08

View file

@ -204,9 +204,9 @@ const Chat = {
} }
}, },
readChat () { readChat () {
if (!(this.currentChatMessageService && this.currentChatMessageService.lastMessage)) { return } if (!(this.currentChatMessageService && this.currentChatMessageService.maxId)) { return }
if (document.hidden) { return } if (document.hidden) { return }
const lastReadId = this.currentChatMessageService.lastMessage.id const lastReadId = this.currentChatMessageService.maxId
this.$store.dispatch('readChat', { id: this.currentChat.id, lastReadId }) this.$store.dispatch('readChat', { id: this.currentChat.id, lastReadId })
}, },
bottomedOut (offset) { bottomedOut (offset) {
@ -244,7 +244,7 @@ const Chat = {
const chatId = chatMessageService.chatId const chatId = chatMessageService.chatId
const fetchOlderMessages = !!maxId const fetchOlderMessages = !!maxId
const sinceId = fetchLatest && chatMessageService.lastMessage && chatMessageService.lastMessage.id const sinceId = fetchLatest && chatMessageService.maxId
this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId }) this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId })
.then((messages) => { .then((messages) => {
@ -303,7 +303,11 @@ const Chat = {
return this.backendInteractor.sendChatMessage(params) return this.backendInteractor.sendChatMessage(params)
.then(data => { .then(data => {
this.$store.dispatch('addChatMessages', { chatId: this.currentChat.id, messages: [data] }).then(() => { this.$store.dispatch('addChatMessages', {
chatId: this.currentChat.id,
messages: [data],
updateMaxId: false
}).then(() => {
this.$nextTick(() => { this.$nextTick(() => {
this.handleResize() this.handleResize()
// When the posting form size changes because of a media attachment, we need an extra resize // When the posting form size changes because of a media attachment, we need an extra resize

View file

@ -2,7 +2,7 @@
.chat-message-wrapper { .chat-message-wrapper {
&.hovered-message-chain { &.hovered-message-chain {
.animated.avatar { .animated.Avatar {
canvas { canvas {
display: none; display: none;
} }

View file

@ -63,7 +63,7 @@
@click.stop.prevent="togglePanel" @click.stop.prevent="togglePanel"
> >
<div class="title"> <div class="title">
<i class="icon-comment-empty" /> <i class="icon-megaphone" />
{{ $t('shoutbox.title') }} {{ $t('shoutbox.title') }}
</div> </div>
</div> </div>

View file

@ -39,13 +39,16 @@
export default { export default {
props: { props: {
large: { large: {
required: false required: false,
type: Boolean,
default: false
}, },
// TODO: Make theme switcher compute theme initially so that contrast // TODO: Make theme switcher compute theme initially so that contrast
// component won't be called without contrast data // component won't be called without contrast data
contrast: { contrast: {
required: false, required: false,
type: Object type: Object,
default: () => ({})
} }
}, },
computed: { computed: {

View file

@ -39,7 +39,7 @@
word-wrap: break-word; word-wrap: break-word;
word-break: break-word; word-break: break-word;
&:hover .animated.avatar { &:hover .animated.Avatar {
canvas { canvas {
display: none; display: none;
} }

View file

@ -47,11 +47,6 @@ const passwordReset = {
if (status === 204) { if (status === 204) {
this.success = true this.success = true
this.error = null this.error = null
} else if (status === 404 || status === 400) {
this.error = this.$t('password_reset.not_found')
this.$nextTick(() => {
this.$refs.email.focus()
})
} else if (status === 429) { } else if (status === 429) {
this.throttled = true this.throttled = true
this.error = this.$t('password_reset.too_many_requests') this.error = this.$t('password_reset.too_many_requests')

View file

@ -17,6 +17,7 @@
<span class="result-percentage"> <span class="result-percentage">
{{ percentageForOption(option.votes_count) }}% {{ percentageForOption(option.votes_count) }}%
</span> </span>
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="option.title_html" /> <span v-html="option.title_html" />
</div> </div>
<div <div
@ -96,6 +97,7 @@
align-items: center; align-items: center;
padding: 0.1em 0.25em; padding: 0.1em 0.25em;
z-index: 1; z-index: 1;
word-break: break-word;
} }
.result-percentage { .result-percentage {
width: 3.5em; width: 3.5em;

View file

@ -555,6 +555,9 @@ const PostStatusForm = {
}, },
updateIdempotencyKey () { updateIdempotencyKey () {
this.idempotencyKey = Date.now().toString() this.idempotencyKey = Date.now().toString()
},
openProfileTab () {
this.$store.dispatch('openSettingsModalTab', 'profile')
} }
} }
} }

View file

@ -23,9 +23,12 @@
tag="p" tag="p"
class="visibility-notice" class="visibility-notice"
> >
<router-link :to="{ name: 'user-settings' }"> <a
href="#"
@click="openProfileTab"
>
{{ $t('post_status.account_not_locked_warning_link') }} {{ $t('post_status.account_not_locked_warning_link') }}
</router-link> </a>
</i18n> </i18n>
<p <p
v-if="!hideScopeNotice && newStatus.visibility === 'public'" v-if="!hideScopeNotice && newStatus.visibility === 'public'"

View file

@ -27,6 +27,34 @@ const SettingsModalContent = {
computed: { computed: {
isLoggedIn () { isLoggedIn () {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
},
open () {
return this.$store.state.interface.settingsModalState !== 'hidden'
}
},
methods: {
onOpen () {
const targetTab = this.$store.state.interface.settingsModalTargetTab
// We're being told to open in specific tab
if (targetTab) {
const tabIndex = this.$refs.tabSwitcher.$slots.default.findIndex(elm => {
return elm.data && elm.data.attrs['data-tab-name'] === targetTab
})
if (tabIndex >= 0) {
this.$refs.tabSwitcher.setTab(tabIndex)
}
}
// Clear the state of target tab, so that next time settings is opened
// it doesn't force it.
this.$store.dispatch('clearSettingsModalTargetTab')
}
},
mounted () {
this.onOpen()
},
watch: {
open: function (value) {
if (value) this.onOpen()
} }
} }
} }

View file

@ -8,6 +8,7 @@
<div <div
:label="$t('settings.general')" :label="$t('settings.general')"
icon="wrench" icon="wrench"
data-tab-name="general"
> >
<GeneralTab /> <GeneralTab />
</div> </div>
@ -15,6 +16,7 @@
v-if="isLoggedIn" v-if="isLoggedIn"
:label="$t('settings.profile_tab')" :label="$t('settings.profile_tab')"
icon="user" icon="user"
data-tab-name="profile"
> >
<ProfileTab /> <ProfileTab />
</div> </div>
@ -22,18 +24,21 @@
v-if="isLoggedIn" v-if="isLoggedIn"
:label="$t('settings.security_tab')" :label="$t('settings.security_tab')"
icon="lock" icon="lock"
data-tab-name="security"
> >
<SecurityTab /> <SecurityTab />
</div> </div>
<div <div
:label="$t('settings.filtering')" :label="$t('settings.filtering')"
icon="filter" icon="filter"
data-tab-name="filtering"
> >
<FilteringTab /> <FilteringTab />
</div> </div>
<div <div
:label="$t('settings.theme')" :label="$t('settings.theme')"
icon="brush" icon="brush"
data-tab-name="theme"
> >
<ThemeTab /> <ThemeTab />
</div> </div>
@ -41,6 +46,7 @@
v-if="isLoggedIn" v-if="isLoggedIn"
:label="$t('settings.notifications')" :label="$t('settings.notifications')"
icon="bell-ringing-o" icon="bell-ringing-o"
data-tab-name="notifications"
> >
<NotificationsTab /> <NotificationsTab />
</div> </div>
@ -48,6 +54,7 @@
v-if="isLoggedIn" v-if="isLoggedIn"
:label="$t('settings.data_import_export_tab')" :label="$t('settings.data_import_export_tab')"
icon="download" icon="download"
data-tab-name="dataImportExport"
> >
<DataImportExportTab /> <DataImportExportTab />
</div> </div>
@ -56,12 +63,14 @@
:label="$t('settings.mutes_and_blocks')" :label="$t('settings.mutes_and_blocks')"
:fullHeight="true" :fullHeight="true"
icon="eye-off" icon="eye-off"
data-tab-name="mutesAndBlocks"
> >
<MutesAndBlocksTab /> <MutesAndBlocksTab />
</div> </div>
<div <div
:label="$t('settings.version.title')" :label="$t('settings.version.title')"
icon="info-circled" icon="info-circled"
data-tab-name="version"
> >
<VersionTab /> <VersionTab />
</div> </div>

View file

@ -278,7 +278,7 @@
/> />
<ContrastRatio <ContrastRatio
:contrast="previewContrast.alertErrorText" :contrast="previewContrast.alertErrorText"
large="true" large
/> />
<ColorInput <ColorInput
v-model="alertWarningColorLocal" v-model="alertWarningColorLocal"
@ -294,7 +294,7 @@
/> />
<ContrastRatio <ContrastRatio
:contrast="previewContrast.alertWarningText" :contrast="previewContrast.alertWarningText"
large="true" large
/> />
<ColorInput <ColorInput
v-model="alertNeutralColorLocal" v-model="alertNeutralColorLocal"
@ -310,7 +310,7 @@
/> />
<ContrastRatio <ContrastRatio
:contrast="previewContrast.alertNeutralText" :contrast="previewContrast.alertNeutralText"
large="true" large
/> />
<OpacityInput <OpacityInput
v-model="alertOpacityLocal" v-model="alertOpacityLocal"
@ -334,7 +334,7 @@
/> />
<ContrastRatio <ContrastRatio
:contrast="previewContrast.badgeNotificationText" :contrast="previewContrast.badgeNotificationText"
large="true" large
/> />
</div> </div>
<div class="color-item"> <div class="color-item">
@ -359,7 +359,7 @@
/> />
<ContrastRatio <ContrastRatio
:contrast="previewContrast.panelText" :contrast="previewContrast.panelText"
large="true" large
/> />
<ColorInput <ColorInput
v-model="panelLinkColorLocal" v-model="panelLinkColorLocal"
@ -369,7 +369,7 @@
/> />
<ContrastRatio <ContrastRatio
:contrast="previewContrast.panelLink" :contrast="previewContrast.panelLink"
large="true" large
/> />
</div> </div>
<div class="color-item"> <div class="color-item">
@ -740,57 +740,57 @@
<ColorInput <ColorInput
v-model="chatBgColorLocal" v-model="chatBgColorLocal"
name="chatBgColor" name="chatBgColor"
:fallback="previewTheme.colors.bg || 1" :fallback="previewTheme.colors.bg"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<h5>{{ $t('settings.style.advanced_colors.chat.incoming') }}</h5> <h5>{{ $t('settings.style.advanced_colors.chat.incoming') }}</h5>
<ColorInput <ColorInput
v-model="chatMessageIncomingBgColorLocal" v-model="chatMessageIncomingBgColorLocal"
name="chatMessageIncomingBgColor" name="chatMessageIncomingBgColor"
:fallback="previewTheme.colors.bg || 1" :fallback="previewTheme.colors.bg"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<ColorInput <ColorInput
v-model="chatMessageIncomingTextColorLocal" v-model="chatMessageIncomingTextColorLocal"
name="chatMessageIncomingTextColor" name="chatMessageIncomingTextColor"
:fallback="previewTheme.colors.text || 1" :fallback="previewTheme.colors.text"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ColorInput <ColorInput
v-model="chatMessageIncomingLinkColorLocal" v-model="chatMessageIncomingLinkColorLocal"
name="chatMessageIncomingLinkColor" name="chatMessageIncomingLinkColor"
:fallback="previewTheme.colors.link || 1" :fallback="previewTheme.colors.link"
:label="$t('settings.links')" :label="$t('settings.links')"
/> />
<ColorInput <ColorInput
v-model="chatMessageIncomingBorderColorLocal" v-model="chatMessageIncomingBorderColorLocal"
name="chatMessageIncomingBorderLinkColor" name="chatMessageIncomingBorderLinkColor"
:fallback="previewTheme.colors.fg || 1" :fallback="previewTheme.colors.fg"
:label="$t('settings.style.advanced_colors.chat.border')" :label="$t('settings.style.advanced_colors.chat.border')"
/> />
<h5>{{ $t('settings.style.advanced_colors.chat.outgoing') }}</h5> <h5>{{ $t('settings.style.advanced_colors.chat.outgoing') }}</h5>
<ColorInput <ColorInput
v-model="chatMessageOutgoingBgColorLocal" v-model="chatMessageOutgoingBgColorLocal"
name="chatMessageOutgoingBgColor" name="chatMessageOutgoingBgColor"
:fallback="previewTheme.colors.bg || 1" :fallback="previewTheme.colors.bg"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<ColorInput <ColorInput
v-model="chatMessageOutgoingTextColorLocal" v-model="chatMessageOutgoingTextColorLocal"
name="chatMessageOutgoingTextColor" name="chatMessageOutgoingTextColor"
:fallback="previewTheme.colors.text || 1" :fallback="previewTheme.colors.text"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ColorInput <ColorInput
v-model="chatMessageOutgoingLinkColorLocal" v-model="chatMessageOutgoingLinkColorLocal"
name="chatMessageOutgoingLinkColor" name="chatMessageOutgoingLinkColor"
:fallback="previewTheme.colors.link || 1" :fallback="previewTheme.colors.link"
:label="$t('settings.links')" :label="$t('settings.links')"
/> />
<ColorInput <ColorInput
v-model="chatMessageOutgoingBorderColorLocal" v-model="chatMessageOutgoingBorderColorLocal"
name="chatMessageOutgoingBorderLinkColor" name="chatMessageOutgoingBorderLinkColor"
:fallback="previewTheme.colors.bg || 1" :fallback="previewTheme.colors.bg"
:label="$t('settings.style.advanced_colors.chat.border')" :label="$t('settings.style.advanced_colors.chat.border')"
/> />
</div> </div>

View file

@ -90,7 +90,7 @@
@click="toggleDrawer" @click="toggleDrawer"
> >
<router-link :to="{ name: 'chat' }"> <router-link :to="{ name: 'chat' }">
<i class="button-icon icon-chat" /> {{ $t("nav.chat") }} <i class="button-icon icon-megaphone" /> {{ $t("shoutbox.title") }}
</router-link> </router-link>
</li> </li>
</ul> </ul>

View file

@ -71,6 +71,10 @@
v-if="attachmentTypes.includes('unknown')" v-if="attachmentTypes.includes('unknown')"
class="icon-doc" class="icon-doc"
/> />
<span
v-if="status.poll && status.poll.options"
class="icon-chart-bar"
/>
<span <span
v-if="status.card" v-if="status.card"
class="icon-link" class="icon-link"
@ -86,7 +90,7 @@
</a> </a>
</div> </div>
<div v-if="status.poll && status.poll.options"> <div v-if="status.poll && status.poll.options && !hideSubjectStatus">
<poll :base-poll="status.poll" /> <poll :base-poll="status.poll" />
</div> </div>

View file

@ -60,16 +60,19 @@ export default Vue.component('tab-switcher', {
} }
}, },
methods: { methods: {
activateTab (index) { clickTab (index) {
return (e) => { return (e) => {
e.preventDefault() e.preventDefault()
if (typeof this.onSwitch === 'function') { this.setTab(index)
this.onSwitch.call(null, this.$slots.default[index].key) }
} },
this.active = index setTab (index) {
if (this.scrollableTabs) { if (typeof this.onSwitch === 'function') {
this.$refs.contents.scrollTop = 0 this.onSwitch.call(null, this.$slots.default[index].key)
} }
this.active = index
if (this.scrollableTabs) {
this.$refs.contents.scrollTop = 0
} }
} }
}, },
@ -88,7 +91,7 @@ export default Vue.component('tab-switcher', {
<div class={classesWrapper.join(' ')}> <div class={classesWrapper.join(' ')}>
<button <button
disabled={slot.data.attrs.disabled} disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)} onClick={this.clickTab(index)}
class={classesTab.join(' ')}> class={classesTab.join(' ')}>
<img src={slot.data.attrs.image} title={slot.data.attrs['image-tooltip']}/> <img src={slot.data.attrs.image} title={slot.data.attrs['image-tooltip']}/>
{slot.data.attrs.label ? '' : slot.data.attrs.label} {slot.data.attrs.label ? '' : slot.data.attrs.label}
@ -100,7 +103,7 @@ export default Vue.component('tab-switcher', {
<div class={classesWrapper.join(' ')}> <div class={classesWrapper.join(' ')}>
<button <button
disabled={slot.data.attrs.disabled} disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)} onClick={this.clickTab(index)}
class={classesTab.join(' ')} class={classesTab.join(' ')}
type="button" type="button"
> >

View file

@ -354,7 +354,7 @@
align-items: flex-start; align-items: flex-start;
max-height: 56px; max-height: 56px;
.avatar { .Avatar {
flex: 1 0 100%; flex: 1 0 100%;
width: 56px; width: 56px;
height: 56px; height: 56px;
@ -364,7 +364,7 @@
} }
} }
&:hover .avatar { &:hover .Avatar {
--still-image-img: visible; --still-image-img: visible;
--still-image-canvas: hidden; --still-image-canvas: hidden;
} }

View file

@ -478,7 +478,6 @@
"placeholder": "Dein Benutzername oder die zugehörige E-Mail-Adresse", "placeholder": "Dein Benutzername oder die zugehörige E-Mail-Adresse",
"check_email": "Im E-Mail-Posteingang des angebenen Kontos müsste sich jetzt (oder zumindest in Kürze) die E-Mail mit dem Link zum Passwortzurücksetzen befinden.", "check_email": "Im E-Mail-Posteingang des angebenen Kontos müsste sich jetzt (oder zumindest in Kürze) die E-Mail mit dem Link zum Passwortzurücksetzen befinden.",
"return_home": "Zurück zur Heimseite", "return_home": "Zurück zur Heimseite",
"not_found": "Benutzername/E-Mail-Adresse nicht gefunden. Vertippt?",
"too_many_requests": "Kurze Pause. Zu viele Versuche. Bitte, später nochmal probieren.", "too_many_requests": "Kurze Pause. Zu viele Versuche. Bitte, später nochmal probieren.",
"password_reset_disabled": "Passwortzurücksetzen deaktiviert. Bitte Administrator kontaktieren.", "password_reset_disabled": "Passwortzurücksetzen deaktiviert. Bitte Administrator kontaktieren.",
"password_reset_required": "Passwortzurücksetzen erforderlich.", "password_reset_required": "Passwortzurücksetzen erforderlich.",

View file

@ -113,7 +113,6 @@
"about": "About", "about": "About",
"administration": "Administration", "administration": "Administration",
"back": "Back", "back": "Back",
"chat": "Local Chat",
"friend_requests": "Follow Requests", "friend_requests": "Follow Requests",
"mentions": "Mentions", "mentions": "Mentions",
"interactions": "Interactions", "interactions": "Interactions",
@ -775,7 +774,6 @@
"placeholder": "Your email or username", "placeholder": "Your email or username",
"check_email": "Check your email for a link to reset your password.", "check_email": "Check your email for a link to reset your password.",
"return_home": "Return to the home page", "return_home": "Return to the home page",
"not_found": "We couldn't find that email or username.",
"too_many_requests": "You have reached the limit of attempts, try again later.", "too_many_requests": "You have reached the limit of attempts, try again later.",
"password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.", "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.",
"password_reset_required": "You must reset your password to log in.", "password_reset_required": "You must reset your password to log in.",

View file

@ -776,7 +776,6 @@
"password_reset_required": "Vi devas restarigi vian pasvorton por saluti.", "password_reset_required": "Vi devas restarigi vian pasvorton por saluti.",
"password_reset_disabled": "Restarigado de pasvortoj estas malŝaltita. Bonvolu kontakti la administranton de via nodo.", "password_reset_disabled": "Restarigado de pasvortoj estas malŝaltita. Bonvolu kontakti la administranton de via nodo.",
"too_many_requests": "Vi atingis la limon de provoj, reprovu pli poste.", "too_many_requests": "Vi atingis la limon de provoj, reprovu pli poste.",
"not_found": "Ni ne trovis tiun retpoŝtadreson aŭ uzantonomon.",
"return_home": "Reiri al la hejmpaĝo", "return_home": "Reiri al la hejmpaĝo",
"check_email": "Kontrolu vian retpoŝton pro ligilo por restarigi vian pasvorton.", "check_email": "Kontrolu vian retpoŝton pro ligilo por restarigi vian pasvorton.",
"placeholder": "Via retpoŝtadreso aŭ uzantonomo", "placeholder": "Via retpoŝtadreso aŭ uzantonomo",

View file

@ -13,7 +13,8 @@
"scope_options": "Opciones del alcance de la visibilidad", "scope_options": "Opciones del alcance de la visibilidad",
"text_limit": "Límite de caracteres", "text_limit": "Límite de caracteres",
"title": "Características", "title": "Características",
"who_to_follow": "A quién seguir" "who_to_follow": "A quién seguir",
"pleroma_chat_messages": "Chat de Pleroma"
}, },
"finder": { "finder": {
"error_fetching_user": "Error al buscar usuario", "error_fetching_user": "Error al buscar usuario",
@ -31,7 +32,13 @@
"disable": "Inhabilitar", "disable": "Inhabilitar",
"enable": "Habilitar", "enable": "Habilitar",
"confirm": "Confirmar", "confirm": "Confirmar",
"verify": "Verificar" "verify": "Verificar",
"peek": "Ojear",
"close": "Cerrar",
"dismiss": "Descartar",
"retry": "Inténtalo de nuevo",
"error_retry": "Por favor, inténtalo de nuevo",
"loading": "Cargando…"
}, },
"image_cropper": { "image_cropper": {
"crop_picture": "Recortar la foto", "crop_picture": "Recortar la foto",
@ -41,7 +48,7 @@
}, },
"importer": { "importer": {
"submit": "Enviar", "submit": "Enviar",
"success": "Importado con éxito", "success": "Importado con éxito.",
"error": "Se ha producido un error al importar el archivo." "error": "Se ha producido un error al importar el archivo."
}, },
"login": { "login": {
@ -77,21 +84,27 @@
"dms": "Mensajes Directos", "dms": "Mensajes Directos",
"public_tl": "Línea Temporal Pública", "public_tl": "Línea Temporal Pública",
"timeline": "Línea Temporal", "timeline": "Línea Temporal",
"twkn": "Toda La Red Conocida", "twkn": "Red Conocida",
"user_search": "Búsqueda de Usuarios", "user_search": "Búsqueda de Usuarios",
"search": "Buscar", "search": "Buscar",
"who_to_follow": "A quién seguir", "who_to_follow": "A quién seguir",
"preferences": "Preferencias" "preferences": "Preferencias",
"chats": "Chats",
"timelines": "Líneas de Tiempo",
"bookmarks": "Marcadores"
}, },
"notifications": { "notifications": {
"broken_favorite": "Estado desconocido, buscándolo...", "broken_favorite": "Estado desconocido, buscándolo",
"favorited_you": "le gusta tu estado", "favorited_you": "le gusta tu estado",
"followed_you": "empezó a seguirte", "followed_you": "empezó a seguirte",
"load_older": "Cargar notificaciones antiguas", "load_older": "Cargar notificaciones antiguas",
"notifications": "Notificaciones", "notifications": "Notificaciones",
"read": "¡Leído!", "read": "¡Leído!",
"repeated_you": "repitió tu estado", "repeated_you": "repitió tu estado",
"no_more_notifications": "No hay más notificaciones" "no_more_notifications": "No hay más notificaciones",
"reacted_with": "reaccionó con {0}",
"migrated_to": "migrado a",
"follow_request": "quiere seguirte"
}, },
"polls": { "polls": {
"add_poll": "Añadir encuesta", "add_poll": "Añadir encuesta",
@ -114,7 +127,9 @@
"search_emoji": "Buscar un emoji", "search_emoji": "Buscar un emoji",
"add_emoji": "Insertar un emoji", "add_emoji": "Insertar un emoji",
"custom": "Emojis personalizados", "custom": "Emojis personalizados",
"unicode": "Emojis unicode" "unicode": "Emojis unicode",
"load_all": "Cargando todos los {emojiAmount} emoji",
"load_all_hint": "Cargado el primer emoji {saneAmount}, cargar todos los emoji puede causar problemas de rendimiento."
}, },
"stickers": { "stickers": {
"add_sticker": "Añadir Pegatina" "add_sticker": "Añadir Pegatina"
@ -122,7 +137,8 @@
"interactions": { "interactions": {
"favs_repeats": "Favoritos y Repetidos", "favs_repeats": "Favoritos y Repetidos",
"follows": "Nuevos seguidores", "follows": "Nuevos seguidores",
"load_older": "Cargar interacciones más antiguas" "load_older": "Cargar interacciones más antiguas",
"moves": "Usuario Migrado"
}, },
"post_status": { "post_status": {
"new_status": "Publicar un nuevo estado", "new_status": "Publicar un nuevo estado",
@ -142,7 +158,7 @@
"posting": "Publicando", "posting": "Publicando",
"scope_notice": { "scope_notice": {
"public": "Esta publicación será visible para todo el mundo", "public": "Esta publicación será visible para todo el mundo",
"private": "Esta publicación solo será visible para tus seguidores.", "private": "Esta publicación solo será visible para tus seguidores",
"unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida" "unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida"
}, },
"scope": { "scope": {
@ -150,7 +166,12 @@
"private": "Solo-seguidores - Solo tus seguidores leerán la publicación", "private": "Solo-seguidores - Solo tus seguidores leerán la publicación",
"public": "Público - Entradas visibles en las Líneas Temporales Públicas", "public": "Público - Entradas visibles en las Líneas Temporales Públicas",
"unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas" "unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas"
} },
"media_description_error": "Error al actualizar el archivo, inténtalo de nuevo",
"empty_status_error": "No se puede publicar un estado vacío y sin archivos adjuntos",
"preview_empty": "Vacío",
"preview": "Vista previa",
"media_description": "Descripción multimedia"
}, },
"registration": { "registration": {
"bio": "Biografía", "bio": "Biografía",
@ -189,7 +210,7 @@
"generate_new_recovery_codes": "Generar códigos de recuperación nuevos", "generate_new_recovery_codes": "Generar códigos de recuperación nuevos",
"warning_of_generate_new_codes": "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.", "warning_of_generate_new_codes": "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.",
"recovery_codes": "Códigos de recuperación.", "recovery_codes": "Códigos de recuperación.",
"waiting_a_recovery_codes": "Recibiendo códigos de respaldo", "waiting_a_recovery_codes": "Recibiendo códigos de respaldo",
"recovery_codes_warning": "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.", "recovery_codes_warning": "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.",
"authentication_methods": "Métodos de autentificación", "authentication_methods": "Métodos de autentificación",
"scan": { "scan": {
@ -232,7 +253,7 @@
"default_vis": "Alcance de visibilidad por defecto", "default_vis": "Alcance de visibilidad por defecto",
"delete_account": "Eliminar la cuenta", "delete_account": "Eliminar la cuenta",
"discoverable": "Permitir la aparición de esta cuenta en los resultados de búsqueda y otros servicios", "discoverable": "Permitir la aparición de esta cuenta en los resultados de búsqueda y otros servicios",
"delete_account_description": "Eliminar para siempre la cuenta y todos los mensajes.", "delete_account_description": "Eliminar para siempre los datos y desactivar la cuenta.",
"pad_emoji": "Rellenar con espacios al agregar emojis desde el selector", "pad_emoji": "Rellenar con espacios al agregar emojis desde el selector",
"delete_account_error": "Hubo un error al eliminar tu cuenta. Si el fallo persiste, ponte en contacto con el administrador de tu instancia.", "delete_account_error": "Hubo un error al eliminar tu cuenta. Si el fallo persiste, ponte en contacto con el administrador de tu instancia.",
"delete_account_instructions": "Escribe tu contraseña para confirmar la eliminación de tu cuenta.", "delete_account_instructions": "Escribe tu contraseña para confirmar la eliminación de tu cuenta.",
@ -253,7 +274,7 @@
"max_thumbnails": "Cantidad máxima de miniaturas por publicación", "max_thumbnails": "Cantidad máxima de miniaturas por publicación",
"hide_isp": "Ocultar el panel específico de la instancia", "hide_isp": "Ocultar el panel específico de la instancia",
"preload_images": "Precargar las imágenes", "preload_images": "Precargar las imágenes",
"use_one_click_nsfw": "Abrir los adjuntos NSFW con un solo click.", "use_one_click_nsfw": "Abrir los adjuntos NSFW con un solo click",
"hide_post_stats": "Ocultar las estadísticas de las entradas (p.ej. el número de favoritos)", "hide_post_stats": "Ocultar las estadísticas de las entradas (p.ej. el número de favoritos)",
"hide_user_stats": "Ocultar las estadísticas del usuario (p.ej. el número de seguidores)", "hide_user_stats": "Ocultar las estadísticas del usuario (p.ej. el número de seguidores)",
"hide_filtered_statuses": "Ocultar estados filtrados", "hide_filtered_statuses": "Ocultar estados filtrados",
@ -299,7 +320,7 @@
"valid_until": "Válido hasta", "valid_until": "Válido hasta",
"revoke_token": "Revocar", "revoke_token": "Revocar",
"panelRadius": "Paneles", "panelRadius": "Paneles",
"pause_on_unfocused": "Parar la transmisión cuando no estés en foco.", "pause_on_unfocused": "Parar la transmisión cuando no estés en foco",
"presets": "Por defecto", "presets": "Por defecto",
"profile_background": "Fondo del Perfil", "profile_background": "Fondo del Perfil",
"profile_banner": "Cabecera del Perfil", "profile_banner": "Cabecera del Perfil",
@ -355,7 +376,24 @@
"save_load_hint": "Las opciones \"Mantener\" conservan las opciones configuradas actualmente al seleccionar o cargar temas, también almacena dichas opciones al exportar un tema. Cuando se desactiven todas las casillas de verificación, el tema de exportación lo guardará todo.", "save_load_hint": "Las opciones \"Mantener\" conservan las opciones configuradas actualmente al seleccionar o cargar temas, también almacena dichas opciones al exportar un tema. Cuando se desactiven todas las casillas de verificación, el tema de exportación lo guardará todo.",
"reset": "Reiniciar", "reset": "Reiniciar",
"clear_all": "Limpiar todo", "clear_all": "Limpiar todo",
"clear_opacity": "Limpiar opacidad" "clear_opacity": "Limpiar opacidad",
"help": {
"snapshot_source_mismatch": "Conflicto de versiones: lo más probable es que el frontend se haya revertido y actualizado nuevamente, si cambió el tema con una versión anterior del frontend, lo más probable es que desee usar la versión anterior; de lo contrario, use la nueva versión.",
"migration_napshot_gone": "Por alguna razón, faltaba la instantánea, algunas cosas podrían verse diferentes de lo que recuerdas.",
"migration_snapshot_ok": "Solo para estar seguro, se cargó la instantánea del tema. Puede intentar cargar los datos del tema.",
"fe_downgraded": "Versión de PleromaFE revertida.",
"fe_upgraded": "El creador de temas de PleromaFE se actualizó después de la actualización de la versión.",
"snapshot_missing": "No había ninguna instantánea del tema en el archivo, por lo que podría verse diferente de lo previsto originalmente.",
"snapshot_present": "Se ha cargado una instantánea del tema, por lo que todos los valores se sobrescriben. De lo contrario, puede cargar el tema por completo.",
"older_version_imported": "El archivo que ha importado se creó en una versión anterior del frontend actual.",
"v2_imported": "El archivo que ha importado fue creado para un frontend más antiguo. Intentamos maximizar la compatibilidad, pero aún podría haber inconsistencias.",
"future_version_imported": "El archivo que ha importado se creó para una versión más reciente del frontend.",
"upgraded_from_v2": "PleromaFE se ha actualizado, el tema podría verse un poco diferente de lo que recuerdas."
},
"use_source": "Nueva versión",
"use_snapshot": "Versión antigua",
"keep_as_is": "Mantener como está",
"load_theme": "Cargar tema"
}, },
"common": { "common": {
"color": "Color", "color": "Color",
@ -390,7 +428,26 @@
"borders": "Bordes", "borders": "Bordes",
"buttons": "Botones", "buttons": "Botones",
"inputs": "Campos de entrada", "inputs": "Campos de entrada",
"faint_text": "Texto desvanecido" "faint_text": "Texto desvanecido",
"alert_neutral": "Neutral",
"chat": {
"border": "Borde",
"outgoing": "Salientes",
"incoming": "Entrantes"
},
"tabs": "Pestañas",
"toggled": "Intercambiado",
"disabled": "Deshabilitado",
"selectedMenu": "Elemento del menú seleccionado",
"selectedPost": "Publicación seleccionada",
"pressed": "Presionado",
"highlight": "Elementos destacados",
"icons": "Iconos",
"poll": "Gráfico de la encuesta",
"underlay": "Subrayado",
"popover": "Sugerencias, menús, superposiciones",
"post": "Publicaciones/Biografías de Usuarios",
"alert_warning": "Precaución"
}, },
"radii": { "radii": {
"_tab_label": "Redondez" "_tab_label": "Redondez"
@ -423,7 +480,8 @@
"buttonPressed": "Botón (presionado)", "buttonPressed": "Botón (presionado)",
"buttonPressedHover": "Botón (presionado+encima)", "buttonPressedHover": "Botón (presionado+encima)",
"input": "Campo de entrada" "input": "Campo de entrada"
} },
"hintV3": "Para las sombras, también puede usar la notación {0} para usar otro espacio de color."
}, },
"fonts": { "fonts": {
"_tab_label": "Fuentes", "_tab_label": "Fuentes",
@ -458,7 +516,42 @@
"title": "Versión", "title": "Versión",
"backend_version": "Versión del Backend", "backend_version": "Versión del Backend",
"frontend_version": "Versión del Frontend" "frontend_version": "Versión del Frontend"
} },
"notification_visibility_moves": "Usuario Migrado",
"greentext": "Texto verde (meme arrows)",
"notification_setting_hide_notification_contents": "Ocultar el remitente y el contenido de las notificaciones push",
"notification_setting_privacy": "Privacidad",
"notification_setting_block_from_strangers": "Bloquea las notificaciones de los usuarios que no sigues",
"notification_setting_filters": "Filtros",
"fun": "Divertido",
"type_domains_to_mute": "Buscar dominios para silenciar",
"useStreamingApiWarning": "(no recomendado, experimental, puede omitir publicaciones)",
"useStreamingApi": "Recibir entradas y notificaciones en tiempo real",
"user_mutes": "Usuarios",
"reset_profile_background": "Restablecer el fondo de pantalla",
"reset_background_confirm": "¿Estás seguro de restablecer el fondo de pantalla?",
"reset_banner_confirm": "¿Estás seguro de restablecer la imagen del banner?",
"reset_avatar_confirm": "¿Estás seguro de restablecer la imagen de avatar?",
"reset_profile_banner": "Restabler imagen del banner del perfil",
"reset_avatar": "Restablecer avatar",
"notification_visibility_emoji_reactions": "Reacciones",
"new_email": "Nuevo correo electrónico",
"profile_fields": {
"value": "Contenido",
"name": "Etiqueta",
"add_field": "Añadir un campo",
"label": "Metadatos del perfil"
},
"accent": "Acento",
"emoji_reactions_on_timeline": "Mostrar las reacciones de emoji en la línea de tiempo",
"domain_mutes": "Dominios",
"mutes_and_blocks": "Silenciado y Bloqueados",
"chatMessageRadius": "Mensaje de chat",
"changed_email": "¡Correo electrónico modificado correctamente!",
"change_email_error": "Ha ocurrido un error al intentar modificar tu correo electrónico.",
"change_email": "Modificar el correo electrónico",
"bot": "Esta cuenta es un bot",
"allow_following_move": "Permitir el seguimiento automático, cuando la cuenta que sigues se traslada a otra instancia"
}, },
"time": { "time": {
"day": "{0} día", "day": "{0} día",
@ -504,7 +597,8 @@
"show_new": "Mostrar lo nuevo", "show_new": "Mostrar lo nuevo",
"up_to_date": "Actualizado", "up_to_date": "Actualizado",
"no_more_statuses": "No hay más estados", "no_more_statuses": "No hay más estados",
"no_statuses": "Sin estados" "no_statuses": "Sin estados",
"reload": "Recargar"
}, },
"status": { "status": {
"favorites": "Favoritos", "favorites": "Favoritos",
@ -517,7 +611,17 @@
"reply_to": "Respondiendo a", "reply_to": "Respondiendo a",
"replies_list": "Respuestas:", "replies_list": "Respuestas:",
"mute_conversation": "Silenciar la conversación", "mute_conversation": "Silenciar la conversación",
"unmute_conversation": "Mostrar la conversación" "unmute_conversation": "Mostrar la conversación",
"hide_content": "Ocultar el contenido",
"show_content": "Mostrar el contenido",
"hide_full_subject": "Ocultar el tema completo",
"show_full_subject": "Mostrar el tema completo",
"thread_muted_and_words": ", contiene:",
"thread_muted": "Conversación silenciada",
"copy_link": "Copiar el enlace al estado",
"status_unavailable": "Estado no disponible",
"bookmark": "Marcar",
"unbookmark": "Desmarcar"
}, },
"user_card": { "user_card": {
"approve": "Aprobar", "approve": "Aprobar",
@ -546,11 +650,11 @@
"subscribe": "Suscribirse", "subscribe": "Suscribirse",
"unsubscribe": "Desuscribirse", "unsubscribe": "Desuscribirse",
"unblock": "Desbloquear", "unblock": "Desbloquear",
"unblock_progress": "Desbloqueando...", "unblock_progress": "Desbloqueando",
"block_progress": "Bloqueando...", "block_progress": "Bloqueando",
"unmute": "Quitar silencio", "unmute": "Dejar de silenciar",
"unmute_progress": "Quitando silencio...", "unmute_progress": "Quitando silencio",
"mute_progress": "Silenciando...", "mute_progress": "Silenciando",
"admin_menu": { "admin_menu": {
"moderation": "Moderación", "moderation": "Moderación",
"grant_admin": "Conceder permisos de Administrador", "grant_admin": "Conceder permisos de Administrador",
@ -564,12 +668,16 @@
"strip_media": "Eliminar archivos multimedia de las publicaciones", "strip_media": "Eliminar archivos multimedia de las publicaciones",
"force_unlisted": "Forzar que se publique en el modo -Sin Listar-", "force_unlisted": "Forzar que se publique en el modo -Sin Listar-",
"sandbox": "Forzar que se publique solo para tus seguidores", "sandbox": "Forzar que se publique solo para tus seguidores",
"disable_remote_subscription": "No permitir que usuarios de instancias remotas te siga.", "disable_remote_subscription": "No permitir que usuarios de instancias remotas te siga",
"disable_any_subscription": "No permitir que ningún usuario te siga", "disable_any_subscription": "No permitir que ningún usuario te siga",
"quarantine": "No permitir publicaciones de usuarios de instancias remotas", "quarantine": "No permitir publicaciones de usuarios de instancias remotas",
"delete_user": "Eliminar usuario", "delete_user": "Eliminar usuario",
"delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer." "delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer."
} },
"show_repeats": "Mostrar repetidos",
"hide_repeats": "Ocultar repetidos",
"message": "Mensaje",
"hidden": "Oculto"
}, },
"user_profile": { "user_profile": {
"timeline_title": "Linea Temporal del Usuario", "timeline_title": "Linea Temporal del Usuario",
@ -594,7 +702,11 @@
"repeat": "Repetir", "repeat": "Repetir",
"reply": "Contestar", "reply": "Contestar",
"favorite": "Favorito", "favorite": "Favorito",
"user_settings": "Ajustes de usuario" "user_settings": "Ajustes de usuario",
"bookmark": "Marcador",
"reject_follow_request": "Rechazar la solicitud de seguimiento",
"accept_follow_request": "Aceptar la solicitud de seguimiento",
"add_reaction": "Añadir Reacción"
}, },
"upload": { "upload": {
"error": { "error": {
@ -624,8 +736,78 @@
"placeholder": "Su correo electrónico o nombre de usuario", "placeholder": "Su correo electrónico o nombre de usuario",
"check_email": "Revise su correo electrónico para obtener un enlace para restablecer su contraseña.", "check_email": "Revise su correo electrónico para obtener un enlace para restablecer su contraseña.",
"return_home": "Volver a la página de inicio", "return_home": "Volver a la página de inicio",
"not_found": "No pudimos encontrar ese correo electrónico o nombre de usuario.",
"too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.", "too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.",
"password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia." "password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia.",
"password_reset_required_but_mailer_is_disabled": "Debes restablecer la contraseña, pero el restablecimiento de contraseñas está deshabilitado. Por favor contacta con el administrador de la instancia.",
"password_reset_required": "Debes restablecer la contraseña para iniciar sesión."
},
"errors": {
"storage_unavailable": "Pleroma no pudo acceder al almacenamiento del navegador. Su inicio de sesión o su configuración local no se guardarán y puede encontrar problemas inesperados. Intente habilitar las cookies."
},
"domain_mute_card": {
"unmute_progress": "Quitando silencio…",
"unmute": "Dejar de silenciar",
"mute_progress": "Silenciando…",
"mute": "Silenciar"
},
"about": {
"mrf": {
"simple": {
"accept_desc": "Esta instancia solo acepta mensajes de las siguientes instancias:",
"media_nsfw_desc": "Esta instancia obliga a que los archivos multimedia se establezcan como sensibles en las publicaciones de las siguientes instancias:",
"media_nsfw": "Forzar Multimedia Como Sensible",
"media_removal_desc": "Esta instancia elimina los archivos multimedia de las publicaciones de las siguientes instancias:",
"media_removal": "Eliminar Multimedia",
"quarantine": "Cuarentena",
"ftl_removal_desc": "Esta instancia elimina las siguientes instancias de la línea de tiempo \"Toda la red conocida\":",
"ftl_removal": "Eliminar de la línea de tiempo \"Toda La Red Conocida\"",
"quarantine_desc": "Esta instancia enviará solo publicaciones públicas a las siguientes instancias:",
"simple_policies": "Políticas sobre instancias específicas",
"reject_desc": "Esta instancia no aceptará mensajes de las siguientes instancias:",
"reject": "Rechazar",
"accept": "Aceptar"
},
"mrf_policies_desc": "Las políticas MRF manipulan la federación de esta instancia con el resto del fediverso. Las siguientes políticas están habilitadas:",
"mrf_policies": "Habilitar políticas MRF",
"keyword": {
"ftl_removal": "Eliminar de la línea de tiempo \"Toda La Red Conocida\"",
"keyword_policies": "Política de Palabras Clave",
"is_replaced_by": "→",
"replace": "Reemplazar",
"reject": "Rechazar"
},
"federation": "Federación"
},
"staff": "Equipo"
},
"shoutbox": {
"title": "Jaula de Grillos"
},
"remote_user_resolver": {
"remote_user_resolver": "Resolución de usuario remoto",
"error": "No encontrado.",
"searching_for": "Buscando"
},
"chats": {
"chats": "Chats",
"empty_chat_list_placeholder": "Aún no tienes ninguna conversación. ¡Inicia una nueva conversación!",
"error_sending_message": "Algo salió mal al enviar el mensaje.",
"error_loading_chat": "Algo salió mal al cargar el chat.",
"delete_confirm": "¿Realmente quieres borrar este mensaje?",
"more": "Más",
"empty_message_error": "No puedes publicar un mensaje vacío",
"new": "Nueva conversación",
"delete": "Borrar",
"message_user": "Mensaje de {nickname}",
"you": "Tú:"
},
"display_date": {
"today": "Hoy"
},
"file_type": {
"file": "Archivo",
"image": "Imagen",
"video": "Vídeo",
"audio": "Audio"
} }
} }

View file

@ -232,7 +232,7 @@
"default_vis": "Lehenetsitako ikusgaitasunak", "default_vis": "Lehenetsitako ikusgaitasunak",
"delete_account": "Ezabatu kontua", "delete_account": "Ezabatu kontua",
"discoverable": "Baimendu zure kontua kanpo bilaketa-emaitzetan eta bestelako zerbitzuetan agertzea", "discoverable": "Baimendu zure kontua kanpo bilaketa-emaitzetan eta bestelako zerbitzuetan agertzea",
"delete_account_description": "Betirako ezabatu zure kontua eta zure mezu guztiak", "delete_account_description": "Betirako ezabatu zure datuak eta desaktibatu kontua.",
"pad_emoji": "Zuriuneak gehitu emoji bat aukeratzen denean", "pad_emoji": "Zuriuneak gehitu emoji bat aukeratzen denean",
"delete_account_error": "Arazo bat gertatu da zure kontua ezabatzerakoan. Arazoa jarraitu eskero, administratzailearekin harremanetan jarri.", "delete_account_error": "Arazo bat gertatu da zure kontua ezabatzerakoan. Arazoa jarraitu eskero, administratzailearekin harremanetan jarri.",
"delete_account_instructions": "Idatzi zure pasahitza kontua ezabatzeko.", "delete_account_instructions": "Idatzi zure pasahitza kontua ezabatzeko.",
@ -626,10 +626,17 @@
"placeholder": "Zure e-posta edo erabiltzaile izena", "placeholder": "Zure e-posta edo erabiltzaile izena",
"check_email": "Begiratu zure posta elektronikoa pasahitza berrezarri ahal izateko.", "check_email": "Begiratu zure posta elektronikoa pasahitza berrezarri ahal izateko.",
"return_home": "Itzuli hasierara", "return_home": "Itzuli hasierara",
"not_found": "Ezin izan dugu helbide elektroniko edo erabiltzaile hori aurkitu.",
"too_many_requests": "Saiakera gehiegi burutu ditzu, saiatu berriro geroxeago.", "too_many_requests": "Saiakera gehiegi burutu ditzu, saiatu berriro geroxeago.",
"password_reset_disabled": "Pasahitza berrezartzea debekatuta dago. Mesedez, jarri harremanetan instantzia administratzailearekin.", "password_reset_disabled": "Pasahitza berrezartzea debekatuta dago. Mesedez, jarri harremanetan instantzia administratzailearekin.",
"password_reset_required": "Pasahitza berrezarri behar duzu saioa hasteko.", "password_reset_required": "Pasahitza berrezarri behar duzu saioa hasteko.",
"password_reset_required_but_mailer_is_disabled": "Pasahitza berrezarri behar duzu, baina pasahitza berrezartzeko aukera desgaituta dago. Mesedez, jarri harremanetan instantziaren administratzailearekin." "password_reset_required_but_mailer_is_disabled": "Pasahitza berrezarri behar duzu, baina pasahitza berrezartzeko aukera desgaituta dago. Mesedez, jarri harremanetan instantziaren administratzailearekin."
},
"about": {
"mrf": {
"keyword": {
"keyword_policies": "Gako-hitz politika"
},
"federation": "Federazioa"
}
} }
} }

View file

@ -752,7 +752,6 @@
"password_reset": "Salasanan nollaus", "password_reset": "Salasanan nollaus",
"placeholder": "Sähköpostiosoite tai käyttäjänimi", "placeholder": "Sähköpostiosoite tai käyttäjänimi",
"return_home": "Palaa etusivulle", "return_home": "Palaa etusivulle",
"not_found": "Sähköpostiosoitetta tai käyttäjänimeä ei löytynyt.",
"too_many_requests": "Olet käyttänyt kaikki yritykset, yritä uudelleen myöhemmin.", "too_many_requests": "Olet käyttänyt kaikki yritykset, yritä uudelleen myöhemmin.",
"password_reset_required": "Sinun täytyy vaihtaa salasana kirjautuaksesi." "password_reset_required": "Sinun täytyy vaihtaa salasana kirjautuaksesi."
}, },

View file

@ -730,7 +730,6 @@
"instruction": "Entrer votre address de courriel ou votre nom utilisateur. Nous enverrons un lien pour changer votre mot de passe.", "instruction": "Entrer votre address de courriel ou votre nom utilisateur. Nous enverrons un lien pour changer votre mot de passe.",
"placeholder": "Votre email ou nom d'utilisateur", "placeholder": "Votre email ou nom d'utilisateur",
"return_home": "Retourner à la page d'accueil", "return_home": "Retourner à la page d'accueil",
"not_found": "Email ou nom d'utilisateur inconnu.",
"too_many_requests": "Vos avez atteint la limite d'essais, essayez plus tard.", "too_many_requests": "Vos avez atteint la limite d'essais, essayez plus tard.",
"password_reset_required": "Vous devez changer votre mot de passe pour vous authentifier." "password_reset_required": "Vous devez changer votre mot de passe pour vous authentifier."
} }

View file

@ -745,7 +745,6 @@
"password_reset_required": "Devi reimpostare la tua password per poter continuare.", "password_reset_required": "Devi reimpostare la tua password per poter continuare.",
"password_reset_disabled": "Non puoi azzerare la tua password. Contatta il tuo amministratore.", "password_reset_disabled": "Non puoi azzerare la tua password. Contatta il tuo amministratore.",
"too_many_requests": "Hai raggiunto il numero massimo di tentativi, riprova più tardi.", "too_many_requests": "Hai raggiunto il numero massimo di tentativi, riprova più tardi.",
"not_found": "Non ho trovato questa email o nome utente.",
"return_home": "Torna alla pagina principale", "return_home": "Torna alla pagina principale",
"check_email": "Controlla la tua posta elettronica.", "check_email": "Controlla la tua posta elettronica.",
"placeholder": "La tua email o nome utente", "placeholder": "La tua email o nome utente",

View file

@ -666,7 +666,6 @@
"placeholder": "あなたのメールアドレスかユーザーめい", "placeholder": "あなたのメールアドレスかユーザーめい",
"check_email": "パスワードをリセットするためのリンクがかかれたメールが、とどいているかどうか、みてください。", "check_email": "パスワードをリセットするためのリンクがかかれたメールが、とどいているかどうか、みてください。",
"return_home": "ホームページにもどる", "return_home": "ホームページにもどる",
"not_found": "そのメールアドレスまたはユーザーめいを、みつけることができませんでした。",
"too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。", "too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。",
"password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。", "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。",
"password_reset_required": "ログインするには、パスワードをリセットしてください。", "password_reset_required": "ログインするには、パスワードをリセットしてください。",

View file

@ -625,7 +625,6 @@
"placeholder": "メールアドレスまたはユーザー名", "placeholder": "メールアドレスまたはユーザー名",
"check_email": "パスワードをリセットするためのリンクが記載されたメールが届いているか確認してください。", "check_email": "パスワードをリセットするためのリンクが記載されたメールが届いているか確認してください。",
"return_home": "ホームページに戻る", "return_home": "ホームページに戻る",
"not_found": "メールアドレスまたはユーザー名が見つかりませんでした。",
"too_many_requests": "試行回数の制限に達しました。しばらく時間を置いてから再試行してください。", "too_many_requests": "試行回数の制限に達しました。しばらく時間を置いてから再試行してください。",
"password_reset_disabled": "このインスタンスではパスワードリセットは無効になっています。インスタンスの管理者に連絡してください。" "password_reset_disabled": "このインスタンスではパスワードリセットは無効になっています。インスタンスの管理者に連絡してください。"
} }

View file

@ -677,7 +677,6 @@
"password_reset_required": "Je dient je wachtwoord opnieuw in te stellen om in te kunnen loggen.", "password_reset_required": "Je dient je wachtwoord opnieuw in te stellen om in te kunnen loggen.",
"password_reset_disabled": "Wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.", "password_reset_disabled": "Wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
"too_many_requests": "Je hebt het maximaal aantal pogingen bereikt, probeer het later opnieuw.", "too_many_requests": "Je hebt het maximaal aantal pogingen bereikt, probeer het later opnieuw.",
"not_found": "We kunnen die email of gebruikersnaam niet vinden.",
"return_home": "Terugkeren naar de home pagina", "return_home": "Terugkeren naar de home pagina",
"check_email": "Controleer je email inbox voor een link om je wachtwoord opnieuw in te stellen.", "check_email": "Controleer je email inbox voor een link om je wachtwoord opnieuw in te stellen.",
"placeholder": "Je email of gebruikersnaam", "placeholder": "Je email of gebruikersnaam",

View file

@ -753,7 +753,6 @@
"placeholder": "Twój email lub nazwa użytkownika", "placeholder": "Twój email lub nazwa użytkownika",
"check_email": "Sprawdź pocztę, aby uzyskać link do zresetowania hasła.", "check_email": "Sprawdź pocztę, aby uzyskać link do zresetowania hasła.",
"return_home": "Wróć do strony głównej", "return_home": "Wróć do strony głównej",
"not_found": "Nie mogliśmy znaleźć tego emaila lub nazwy użytkownika.",
"too_many_requests": "Przekroczyłeś(-aś) limit prób, spróbuj ponownie później.", "too_many_requests": "Przekroczyłeś(-aś) limit prób, spróbuj ponownie później.",
"password_reset_disabled": "Resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji.", "password_reset_disabled": "Resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji.",
"password_reset_required": "Musisz zresetować hasło, by się zalogować.", "password_reset_required": "Musisz zresetować hasło, by się zalogować.",

View file

@ -420,7 +420,6 @@
"placeholder": "Ваш email или имя пользователя", "placeholder": "Ваш email или имя пользователя",
"check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.", "check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.",
"return_home": "Вернуться на главную страницу", "return_home": "Вернуться на главную страницу",
"not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.",
"too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.", "too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.",
"password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера." "password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера."
}, },

View file

@ -640,7 +640,6 @@
"placeholder": "你的电邮地址或者用户名", "placeholder": "你的电邮地址或者用户名",
"check_email": "检查你的邮箱,会有一个链接用于重置密码。", "check_email": "检查你的邮箱,会有一个链接用于重置密码。",
"return_home": "回到首页", "return_home": "回到首页",
"not_found": "我们无法找到匹配的邮箱地址或者用户名。",
"too_many_requests": "你触发了尝试的限制,请稍后再试。", "too_many_requests": "你触发了尝试的限制,请稍后再试。",
"password_reset_disabled": "密码重置已经被禁用。请联系你的实例管理员。" "password_reset_disabled": "密码重置已经被禁用。请联系你的实例管理员。"
}, },

View file

@ -143,6 +143,7 @@ const chats = {
const isNewMessage = (chat.lastMessage && chat.lastMessage.id) !== (updatedChat.lastMessage && updatedChat.lastMessage.id) const isNewMessage = (chat.lastMessage && chat.lastMessage.id) !== (updatedChat.lastMessage && updatedChat.lastMessage.id)
chat.lastMessage = updatedChat.lastMessage chat.lastMessage = updatedChat.lastMessage
chat.unread = updatedChat.unread chat.unread = updatedChat.unread
chat.updated_at = updatedChat.updated_at
if (isNewMessage && chat.unread) { if (isNewMessage && chat.unread) {
newChatMessageSideEffects(updatedChat) newChatMessageSideEffects(updatedChat)
} }
@ -181,30 +182,16 @@ const chats = {
setChatsLoading (state, { value }) { setChatsLoading (state, { value }) {
state.chats.loading = value state.chats.loading = value
}, },
addChatMessages (state, { commit, chatId, messages }) { addChatMessages (state, { chatId, messages, updateMaxId }) {
const chatMessageService = state.openedChatMessageServices[chatId] const chatMessageService = state.openedChatMessageServices[chatId]
if (chatMessageService) { if (chatMessageService) {
chatService.add(chatMessageService, { messages: messages.map(parseChatMessage) }) chatService.add(chatMessageService, { messages: messages.map(parseChatMessage), updateMaxId })
commit('refreshLastMessage', { chatId })
} }
}, },
refreshLastMessage (state, { chatId }) { deleteChatMessage (state, { chatId, messageId }) {
const chatMessageService = state.openedChatMessageServices[chatId]
if (chatMessageService) {
const chat = getChatById(state, chatId)
if (chat) {
chat.lastMessage = chatMessageService.lastMessage
if (chatMessageService.lastMessage) {
chat.updated_at = chatMessageService.lastMessage.created_at
}
}
}
},
deleteChatMessage (state, { commit, chatId, messageId }) {
const chatMessageService = state.openedChatMessageServices[chatId] const chatMessageService = state.openedChatMessageServices[chatId]
if (chatMessageService) { if (chatMessageService) {
chatService.deleteMessage(chatMessageService, messageId) chatService.deleteMessage(chatMessageService, messageId)
commit('refreshLastMessage', { chatId })
} }
}, },
resetChatNewMessageCount (state, _value) { resetChatNewMessageCount (state, _value) {

View file

@ -3,6 +3,7 @@ import { set, delete as del } from 'vue'
const defaultState = { const defaultState = {
settingsModalState: 'hidden', settingsModalState: 'hidden',
settingsModalLoaded: false, settingsModalLoaded: false,
settingsModalTargetTab: null,
settings: { settings: {
currentSaveStateNotice: null, currentSaveStateNotice: null,
noticeClearTimeout: null, noticeClearTimeout: null,
@ -62,6 +63,9 @@ const interfaceMod = {
state.settingsModalLoaded = true state.settingsModalLoaded = true
} }
}, },
setSettingsModalTargetTab (state, value) {
state.settingsModalTargetTab = value
},
pushGlobalNotice (state, notice) { pushGlobalNotice (state, notice) {
state.globalNotices.push(notice) state.globalNotices.push(notice)
}, },
@ -97,6 +101,13 @@ const interfaceMod = {
togglePeekSettingsModal ({ commit }) { togglePeekSettingsModal ({ commit }) {
commit('togglePeekSettingsModal') commit('togglePeekSettingsModal')
}, },
clearSettingsModalTargetTab ({ commit }) {
commit('setSettingsModalTargetTab', null)
},
openSettingsModalTab ({ commit }, value) {
commit('setSettingsModalTargetTab', value)
commit('openSettingsModal')
},
pushGlobalNotice ( pushGlobalNotice (
{ commit, dispatch }, { commit, dispatch },
{ {

View file

@ -8,7 +8,7 @@ const empty = (chatId) => {
lastSeenTimestamp: 0, lastSeenTimestamp: 0,
chatId: chatId, chatId: chatId,
minId: undefined, minId: undefined,
lastMessage: undefined maxId: undefined
} }
} }
@ -18,7 +18,7 @@ const clear = (storage) => {
storage.newMessageCount = 0 storage.newMessageCount = 0
storage.lastSeenTimestamp = 0 storage.lastSeenTimestamp = 0
storage.minId = undefined storage.minId = undefined
storage.lastMessage = undefined storage.maxId = undefined
} }
const deleteMessage = (storage, messageId) => { const deleteMessage = (storage, messageId) => {
@ -26,8 +26,9 @@ const deleteMessage = (storage, messageId) => {
storage.messages = storage.messages.filter(m => m.id !== messageId) storage.messages = storage.messages.filter(m => m.id !== messageId)
delete storage.idIndex[messageId] delete storage.idIndex[messageId]
if (storage.lastMessage && (storage.lastMessage.id === messageId)) { if (storage.maxId === messageId) {
storage.lastMessage = _.maxBy(storage.messages, 'id') const lastMessage = _.maxBy(storage.messages, 'id')
storage.maxId = lastMessage.id
} }
if (storage.minId === messageId) { if (storage.minId === messageId) {
@ -36,7 +37,7 @@ const deleteMessage = (storage, messageId) => {
} }
} }
const add = (storage, { messages: newMessages }) => { const add = (storage, { messages: newMessages, updateMaxId = true }) => {
if (!storage) { return } if (!storage) { return }
for (let i = 0; i < newMessages.length; i++) { for (let i = 0; i < newMessages.length; i++) {
const message = newMessages[i] const message = newMessages[i]
@ -48,8 +49,10 @@ const add = (storage, { messages: newMessages }) => {
storage.minId = message.id storage.minId = message.id
} }
if (!storage.lastMessage || message.id > storage.lastMessage.id) { if (!storage.maxId || message.id > storage.maxId) {
storage.lastMessage = message if (updateMaxId) {
storage.maxId = message.id
}
} }
if (!storage.idIndex[message.id]) { if (!storage.idIndex[message.id]) {

View file

@ -3,6 +3,7 @@ import { showDesktopNotification } from '../desktop_notification_utils/desktop_n
export const maybeShowChatNotification = (store, chat) => { export const maybeShowChatNotification = (store, chat) => {
if (!chat.lastMessage) return if (!chat.lastMessage) return
if (store.rootState.chats.currentChatId === chat.id && !document.hidden) return if (store.rootState.chats.currentChatId === chat.id && !document.hidden) return
if (store.rootState.users.currentUser.id === chat.lastMessage.account.id) return
const opts = { const opts = {
tag: chat.lastMessage.id, tag: chat.lastMessage.id,

View file

@ -5,7 +5,7 @@ export const replaceWord = (str, toReplace, replacement) => {
} }
export const wordAtPosition = (str, pos) => { export const wordAtPosition = (str, pos) => {
const words = splitIntoWords(str) const words = splitByWhitespaceBoundary(str)
const wordsWithPosition = addPositionToWords(words) const wordsWithPosition = addPositionToWords(words)
return find(wordsWithPosition, ({ start, end }) => start <= pos && end > pos) return find(wordsWithPosition, ({ start, end }) => start <= pos && end > pos)
@ -34,36 +34,36 @@ export const addPositionToWords = (words) => {
}, []) }, [])
} }
export const splitIntoWords = (str) => { export const splitByWhitespaceBoundary = (str) => {
// Split at word boundaries let result = []
const regex = /\b/ let currentWord = ''
const triggers = /[@#:]+$/ for (let i = 0; i < str.length; i++) {
const currentChar = str[i]
let split = str.split(regex) // Starting a new word
if (!currentWord) {
// Add trailing @ and # to the following word. currentWord = currentChar
const words = reduce(split, (result, word) => { continue
if (result.length > 0) {
let previous = result.pop()
const matches = previous.match(triggers)
if (matches) {
previous = previous.replace(triggers, '')
word = matches[0] + word
}
result.push(previous)
} }
result.push(word) // current character is whitespace while word isn't, or vice versa:
// add our current word to results, start over the current word.
return result if (!!currentChar.trim() !== !!currentWord.trim()) {
}, []) result.push(currentWord)
currentWord = currentChar
return words continue
}
currentWord += currentChar
}
// Add the last word we were working on
if (currentWord) {
result.push(currentWord)
}
return result
} }
const completion = { const completion = {
wordAtPosition, wordAtPosition,
addPositionToWords, addPositionToWords,
splitIntoWords, splitByWhitespaceBoundary,
replaceWord replaceWord
} }

View file

@ -405,6 +405,12 @@
"css": "block", "css": "block",
"code": 59434, "code": 59434,
"src": "fontawesome" "src": "fontawesome"
},
{
"uid": "3e674995cacc2b09692c096ea7eb6165",
"css": "megaphone",
"code": 59435,
"src": "fontawesome"
} }
] ]
} }

View file

@ -33,12 +33,12 @@ describe('chatService', () => {
const chat = chatService.empty() const chat = chatService.empty()
chatService.add(chat, { messages: [ message1 ] }) chatService.add(chat, { messages: [ message1 ] })
expect(chat.lastMessage.id).to.eql(message1.id) expect(chat.maxId).to.eql(message1.id)
expect(chat.minId).to.eql(message1.id) expect(chat.minId).to.eql(message1.id)
expect(chat.newMessageCount).to.eql(1) expect(chat.newMessageCount).to.eql(1)
chatService.add(chat, { messages: [ message2 ] }) chatService.add(chat, { messages: [ message2 ] })
expect(chat.lastMessage.id).to.eql(message2.id) expect(chat.maxId).to.eql(message2.id)
expect(chat.minId).to.eql(message1.id) expect(chat.minId).to.eql(message1.id)
expect(chat.newMessageCount).to.eql(2) expect(chat.newMessageCount).to.eql(2)
@ -60,15 +60,15 @@ describe('chatService', () => {
chatService.add(chat, { messages: [ message2 ] }) chatService.add(chat, { messages: [ message2 ] })
chatService.add(chat, { messages: [ message3 ] }) chatService.add(chat, { messages: [ message3 ] })
expect(chat.lastMessage.id).to.eql(message3.id) expect(chat.maxId).to.eql(message3.id)
expect(chat.minId).to.eql(message1.id) expect(chat.minId).to.eql(message1.id)
chatService.deleteMessage(chat, message3.id) chatService.deleteMessage(chat, message3.id)
expect(chat.lastMessage.id).to.eql(message2.id) expect(chat.maxId).to.eql(message2.id)
expect(chat.minId).to.eql(message1.id) expect(chat.minId).to.eql(message1.id)
chatService.deleteMessage(chat, message1.id) chatService.deleteMessage(chat, message1.id)
expect(chat.lastMessage.id).to.eql(message2.id) expect(chat.maxId).to.eql(message2.id)
expect(chat.minId).to.eql(message2.id) expect(chat.minId).to.eql(message2.id)
}) })
}) })

View file

@ -1,8 +1,8 @@
import { replaceWord, addPositionToWords, wordAtPosition, splitIntoWords } from '../../../../../src/services/completion/completion.js' import { replaceWord, addPositionToWords, wordAtPosition, splitByWhitespaceBoundary } from '../../../../../src/services/completion/completion.js'
describe('addPositiontoWords', () => { describe('addPositiontoWords', () => {
it('adds the position to a word list', () => { it('adds the position to a word list', () => {
const words = ['hey', 'this', 'is', 'fun'] const words = ['hey', ' ', 'this', ' ', 'is', ' ', 'fun']
const expected = [ const expected = [
{ {
@ -11,19 +11,34 @@ describe('addPositiontoWords', () => {
end: 3 end: 3
}, },
{ {
word: 'this', word: ' ',
start: 3, start: 3,
end: 7 end: 4
}, },
{ {
word: 'is', word: 'this',
start: 7, start: 4,
end: 8
},
{
word: ' ',
start: 8,
end: 9 end: 9
}, },
{ {
word: 'fun', word: 'is',
start: 9, start: 9,
end: 11
},
{
word: ' ',
start: 11,
end: 12 end: 12
},
{
word: 'fun',
start: 12,
end: 15
} }
] ]
@ -33,11 +48,11 @@ describe('addPositiontoWords', () => {
}) })
}) })
describe('splitIntoWords', () => { describe('splitByWhitespaceBoundary', () => {
it('splits at whitespace boundaries', () => { it('splits at whitespace boundaries', () => {
const str = 'This is a #nice @test for you, @idiot.' const str = 'This is a #nice @test for you, @idiot@idiot.com'
const expected = ['This', ' ', 'is', ' ', 'a', ' ', '#nice', ' ', '@test', ' ', 'for', ' ', 'you', ', ', '@idiot', '.'] const expected = ['This', ' ', 'is', ' ', 'a', ' ', '#nice', ' ', '@test', ' ', 'for', ' ', 'you,', ' ', '@idiot@idiot.com']
const res = splitIntoWords(str) const res = splitByWhitespaceBoundary(str)
expect(res).to.eql(expected) expect(res).to.eql(expected)
}) })
@ -57,13 +72,13 @@ describe('wordAtPosition', () => {
describe('replaceWord', () => { describe('replaceWord', () => {
it('replaces a word (with start and end) with another word in a given string', () => { it('replaces a word (with start and end) with another word in a given string', () => {
const str = 'hey @take, how are you' const str = 'hey @take , how are you'
const wordsWithPosition = addPositionToWords(splitIntoWords(str)) const wordsWithPosition = addPositionToWords(splitByWhitespaceBoundary(str))
const toReplace = wordsWithPosition[2] const toReplace = wordsWithPosition[2]
expect(toReplace.word).to.eql('@take') expect(toReplace.word).to.eql('@take')
const expected = 'hey @takeshitakenji, how are you' const expected = 'hey @takeshitakenji , how are you'
const res = replaceWord(str, toReplace, '@takeshitakenji') const res = replaceWord(str, toReplace, '@takeshitakenji')
expect(res).to.eql(expected) expect(res).to.eql(expected)
}) })