Page MenuHomePhorge

No OneTemporary

Size
20 KB
Referenced Files
None
Subscribers
None
diff --git a/src/services/completion/completion.js b/src/services/completion/completion.js
index 77b2583523..3abe796ec8 100644
--- a/src/services/completion/completion.js
+++ b/src/services/completion/completion.js
@@ -1,74 +1,73 @@
import { find, reduce } from 'lodash-es'
export const replaceWord = (str, toReplace, replacement) => {
return str.slice(0, toReplace.start) + replacement + str.slice(toReplace.end)
}
export const wordAtPosition = (str, pos) => {
const words = splitByWhitespaceBoundary(str)
const wordsWithPosition = addPositionToWords(words)
return find(wordsWithPosition, ({ start, end }) => start <= pos && end > pos)
}
export const addPositionToWords = (words) => {
return reduce(
words,
(result, word) => {
const data = {
word,
start: 0,
end: word.length,
}
if (result.length > 0) {
const previous = result.pop()
data.start += previous.end
data.end += previous.end
result.push(previous)
}
result.push(data)
return result
},
[],
)
}
export const splitByWhitespaceBoundary = (str) => {
const result = []
let currentWord = ''
- for (let i = 0; i < str.length; i++) {
- const currentChar = str[i]
+ for (let currentChar of str) {
// Starting a new word
if (!currentWord) {
currentWord = currentChar
continue
}
// current character is whitespace while word isn't, or vice versa:
// add our current word to results, start over the current word.
if (!!currentChar.trim() !== !!currentWord.trim()) {
result.push(currentWord)
currentWord = currentChar
continue
}
currentWord += currentChar
}
// Add the last word we were working on
if (currentWord) {
result.push(currentWord)
}
return result
}
const completion = {
wordAtPosition,
addPositionToWords,
splitByWhitespaceBoundary,
replaceWord,
}
export default completion
diff --git a/src/services/export_import/export_import.js b/src/services/export_import/export_import.js
index c6a5ec334b..bea55c3465 100644
--- a/src/services/export_import/export_import.js
+++ b/src/services/export_import/export_import.js
@@ -1,68 +1,68 @@
import utf8 from 'utf8'
export const newExporter = ({
filename = 'data',
mime = 'application/json',
extension = 'json',
getExportedObject,
}) => ({
exportData() {
let stringified
if (mime === 'application/json') {
stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces
} else {
stringified = utf8.encode(getExportedObject()) // Pretty-print and indent with 2 spaces
}
// Create an invisible link with a data url and simulate a click
const e = document.createElement('a')
const realFilename = typeof filename === 'function' ? filename() : filename
e.setAttribute('download', `${realFilename}.${extension}`)
e.setAttribute('href', `data:${mime};base64, ${window.btoa(stringified)}`)
e.style.display = 'none'
document.body.appendChild(e)
e.click()
- document.body.removeChild(e)
+ e.remove()
},
})
export const newImporter = ({
accept = '.json',
parser = (string) => JSON.parse(string),
onImport,
onImportFailure,
validator = () => true,
}) => ({
importData() {
const filePicker = document.createElement('input')
filePicker.setAttribute('type', 'file')
filePicker.setAttribute('accept', accept)
filePicker.addEventListener('change', (event) => {
if (event.target.files[0]) {
const filename = event.target.files[0].name
const reader = new FileReader()
reader.onload = ({ target }) => {
try {
const parsed = parser(target.result, filename)
const validationResult = validator(parsed, filename)
if (validationResult === true) {
onImport(parsed, filename)
} else {
onImportFailure({ validationResult })
}
} catch (error) {
onImportFailure({ error })
}
}
reader.readAsText(event.target.files[0])
}
})
document.body.appendChild(filePicker)
filePicker.click()
- document.body.removeChild(filePicker)
+ filePicker.remove()
},
})
diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js
index ff11775d58..6ceb0a20ef 100644
--- a/src/services/gesture_service/gesture_service.js
+++ b/src/services/gesture_service/gesture_service.js
@@ -1,224 +1,224 @@
const DIRECTION_LEFT = [-1, 0]
const DIRECTION_RIGHT = [1, 0]
const DIRECTION_UP = [0, -1]
const DIRECTION_DOWN = [0, 1]
const BUTTON_LEFT = 0
const deltaCoord = (oldCoord, newCoord) => [
newCoord[0] - oldCoord[0],
newCoord[1] - oldCoord[1],
]
const touchCoord = (touch) => [touch.screenX, touch.screenY]
const touchEventCoord = (e) => touchCoord(e.touches[0])
const pointerEventCoord = (e) => [e.clientX, e.clientY]
-const vectorLength = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1])
+const vectorLength = (v) => Math.hypot(v[0] * v[0], v[1] * v[1])
const perpendicular = (v) => [v[1], -v[0]]
const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1]
const project = (v1, v2) => {
const scalar = dotProduct(v1, v2) / dotProduct(v2, v2)
return [scalar * v2[0], scalar * v2[1]]
}
// direction: either use the constants above or an arbitrary 2d vector.
// threshold: how many Px to move from touch origin before checking if the
// callback should be called.
// divergentTolerance: a scalar for much of divergent direction we tolerate when
// above threshold. for example, with 1.0 we only call the callback if
// divergent component of delta is < 1.0 * direction component of delta.
const swipeGesture = (
direction,
onSwipe,
threshold = 30,
perpendicularTolerance = 1.0,
) => {
return {
direction,
onSwipe,
threshold,
perpendicularTolerance,
_startPos: [0, 0],
_swiping: false,
}
}
const beginSwipe = (event, gesture) => {
gesture._startPos = touchEventCoord(event)
gesture._swiping = true
}
const updateSwipe = (event, gesture) => {
if (!gesture._swiping) return
// movement too small
const delta = deltaCoord(gesture._startPos, touchEventCoord(event))
if (vectorLength(delta) < gesture.threshold) return
// movement is opposite from direction
if (dotProduct(delta, gesture.direction) < 0) return
// movement perpendicular to direction is too much
const towardsDir = project(delta, gesture.direction)
const perpendicularDir = perpendicular(gesture.direction)
const towardsPerpendicular = project(delta, perpendicularDir)
if (
vectorLength(towardsDir) * gesture.perpendicularTolerance <
vectorLength(towardsPerpendicular)
)
return
gesture.onSwipe()
gesture._swiping = false
}
class SwipeAndClickGesture {
// swipePreviewCallback(offsets: Array[Number])
// offsets: the offset vector which the underlying component should move, from the starting position
// swipeEndCallback(sign: 0|-1|1)
// sign: if the swipe does not meet the threshold, 0
// if the swipe meets the threshold in the positive direction, 1
// if the swipe meets the threshold in the negative direction, -1
constructor({
direction,
// swipeStartCallback
swipePreviewCallback,
swipeEndCallback,
swipeCancelCallback,
swipelessClickCallback,
threshold = 30,
perpendicularTolerance = 1.0,
disableClickThreshold = 1,
}) {
const noop = () => {
/* no-op */
}
this.direction = direction
this.swipePreviewCallback = swipePreviewCallback || noop
this.swipeEndCallback = swipeEndCallback || noop
this.swipeCancelCallback = swipeCancelCallback || noop
this.swipelessClickCallback = swipelessClickCallback || noop
this.threshold =
typeof threshold === 'function' ? threshold : () => threshold
this.disableClickThreshold =
typeof disableClickThreshold === 'function'
? disableClickThreshold
: () => disableClickThreshold
this.perpendicularTolerance = perpendicularTolerance
this._reset()
}
_reset() {
this._startPos = [0, 0]
this._pointerId = -1
this._swiping = false
this._swiped = false
this._preventNextClick = false
}
start(event) {
// Only handle left click
if (event.button !== BUTTON_LEFT) {
return
}
this._startPos = pointerEventCoord(event)
this._pointerId = event.pointerId
this._swiping = true
this._swiped = false
}
move(event) {
if (this._swiping && this._pointerId === event.pointerId) {
this._swiped = true
const coord = pointerEventCoord(event)
const delta = deltaCoord(this._startPos, coord)
this.swipePreviewCallback(delta)
}
}
cancel(event) {
if (!this._swiping || this._pointerId !== event.pointerId) {
return
}
this.swipeCancelCallback()
}
end(event) {
if (!this._swiping) {
return
}
if (this._pointerId !== event.pointerId) {
return
}
this._swiping = false
// movement too small
const coord = pointerEventCoord(event)
const delta = deltaCoord(this._startPos, coord)
const sign = (() => {
if (vectorLength(delta) < this.threshold()) {
return 0
}
// movement is opposite from direction
const isPositive = dotProduct(delta, this.direction) > 0
// movement perpendicular to direction is too much
const towardsDir = project(delta, this.direction)
const perpendicularDir = perpendicular(this.direction)
const towardsPerpendicular = project(delta, perpendicularDir)
if (
vectorLength(towardsDir) * this.perpendicularTolerance <
vectorLength(towardsPerpendicular)
) {
return 0
}
return isPositive ? 1 : -1
})()
if (this._swiped) {
this.swipeEndCallback(sign)
}
this._reset()
// Only a mouse will fire click event when
// the end point is far from the starting point
// so for other kinds of pointers do not check
// whether we have swiped
if (
vectorLength(delta) >= this.disableClickThreshold() &&
event.pointerType === 'mouse'
) {
this._preventNextClick = true
}
}
click() {
if (!this._preventNextClick) {
this.swipelessClickCallback()
}
this._reset()
}
}
const GestureService = {
DIRECTION_LEFT,
DIRECTION_RIGHT,
DIRECTION_UP,
DIRECTION_DOWN,
swipeGesture,
beginSwipe,
updateSwipe,
SwipeAndClickGesture,
}
export default GestureService
diff --git a/src/services/locale/locale.service.js b/src/services/locale/locale.service.js
index 47c910791b..8476b5c34b 100644
--- a/src/services/locale/locale.service.js
+++ b/src/services/locale/locale.service.js
@@ -1,51 +1,50 @@
import ISO6391 from 'iso-639-1'
-import { map } from 'lodash-es'
import languagesObject from '../../i18n/messages'
const specialLanguageCodes = {
pdc: 'en',
ja_easy: 'ja',
zh_Hant: 'zh-HANT',
zh: 'zh-Hans',
}
const internalToBrowserLocale = (code) => specialLanguageCodes[code] || code
const internalToBackendLocale = (code) =>
internalToBrowserLocale(code).replace('_', '-')
const internalToBackendLocaleMulti = (codes) => {
const langs = Array.isArray(codes) ? codes : [codes]
return langs.map(internalToBackendLocale).join(',')
}
const getLanguageName = (code) => {
const specialLanguageNames = {
pdc: 'Pennsilfaanisch-Deitsch',
ja_easy: 'やさしいにほんご',
'nan-TW': '臺語(閩南語)',
zh: '简体中文',
zh_Hant: '繁體中文',
}
const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
const browserLocale = internalToBrowserLocale(code)
return (
languageName.charAt(0).toLocaleUpperCase(browserLocale) +
languageName.slice(1)
)
}
-const languages = map(languagesObject.languages, (code) => ({
+const languages = languagesObject.languages.map((code) => ({
code,
name: getLanguageName(code),
})).sort((a, b) => a.name.localeCompare(b.name))
const localeService = {
internalToBrowserLocale,
internalToBackendLocale,
internalToBackendLocaleMulti,
languages,
getLanguageName,
}
export default localeService
diff --git a/src/services/matcher/matcher.service.js b/src/services/matcher/matcher.service.js
index df9e1a073b..98063a4aab 100644
--- a/src/services/matcher/matcher.service.js
+++ b/src/services/matcher/matcher.service.js
@@ -1,30 +1,30 @@
export const mentionMatchesUrl = (attention, url) => {
if (url === attention.statusnet_profile_url) {
return true
}
const [namepart, instancepart] = attention.screen_name.split('@')
const matchstring = new RegExp(
'://' + instancepart + '/.*' + namepart + '$',
'g',
)
return !!url.match(matchstring)
}
/**
* Extract tag name from pleroma or mastodon url.
* i.e https://bikeshed.party/tag/photo or https://quey.org/tags/sky
* @param {string} url
*/
export const extractTagFromUrl = (url) => {
const decoded = decodeURI(url)
// https://git.pleroma.social/pleroma/elixir-libraries/linkify/-/blob/master/lib/linkify/parser.ex
// https://www.pcre.org/original/doc/html/pcrepattern.html
const regex =
- /tag[s]*\/([\p{L}\p{N}_]*[\p{Alphabetic}_·\u{200c}][\p{L}\p{N}_·\p{M}\u{200c}]*)$/gu
+ /tags*\/([\p{L}\p{N}_]*[\p{Alphabetic}_·\u{200c}][\p{L}\p{N}_·\p{M}\u{200c}]*)$/gu
const result = regex.exec(decoded)
if (!result) {
- return false
+ return null
}
return result[1]
}
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index 803a563229..c67dc383b0 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -1,119 +1,119 @@
import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js'
import { muteFilterHits } from '../status_parser/status_parser.js'
import {
isStatusNotification,
prepareNotificationObject,
} from './notification_utils_sw.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
export const ACTIONABLE_NOTIFICATION_TYPES = new Set([
'mention',
'pleroma:report',
'follow_request',
])
const visibleTypes = (notificationVisibility) => {
return [
notificationVisibility.likes && 'like',
notificationVisibility.mentions && 'mention',
notificationVisibility.statuses && 'status',
notificationVisibility.repeats && 'repeat',
notificationVisibility.follows && 'follow',
notificationVisibility.followRequest && 'follow_request',
notificationVisibility.moves && 'move',
notificationVisibility.emojiReactions && 'pleroma:emoji_reaction',
notificationVisibility.reports && 'pleroma:report',
notificationVisibility.polls && 'poll',
].filter(Boolean)
}
export const isValidNotification = (notification) => {
- if (isStatusNotification(notification.type) && !notification.status) {
- return false
+ if (isStatusNotification(notification.type)) {
+ return !notification.status
}
return true
}
const sortById = (a, b) => {
const seqA = Number(a.id)
const seqB = Number(b.id)
const isSeqA = !Number.isNaN(seqA)
const isSeqB = !Number.isNaN(seqB)
if (isSeqA && isSeqB) {
return seqA > seqB ? -1 : 1
} else if (isSeqA && !isSeqB) {
return 1
} else if (!isSeqA && isSeqB) {
return -1
} else {
return a.id > b.id ? -1 : 1
}
}
const isMutedNotification = (muteFilters, notification) => {
if (!notification.status) return false
if (notification.status.muted) return true
return muteFilterHits(muteFilters, notification.status).length > 0
}
export const maybeShowNotification = (
notificationVisibility,
muteFilters,
notification,
i18n,
) => {
if (notification.seen) return
if (!visibleTypes(notificationVisibility).includes(notification.type)) return
if (
notification.type === 'mention' &&
isMutedNotification(muteFilters, notification)
)
return
const notificationObject = prepareNotificationObject(notification, i18n)
showDesktopNotification(notificationObject)
}
export const filteredNotifications = (notificationVisibility, types) => {
// map is just to clone the array since sort mutates it and it causes some issues
const sortedNotifications = useNotificationsStore().data.sort(sortById)
// TODO implement sorting elsewhere and make it optional
return sortedNotifications.filter((notification) =>
(types || visibleTypes(notificationVisibility)).includes(notification.type),
)
}
export const unseenNotifications = (
notificationVisibility,
ignoreInactionableSeen,
) => {
return filteredNotifications(notificationVisibility).filter(
({ seen, type }) => {
if (!ignoreInactionableSeen) return !seen
if (seen) return false
return ACTIONABLE_NOTIFICATION_TYPES.has(type)
},
)
}
export const countExtraNotifications = (
mergedConfig,
unreadChatsCount,
unreadAnnouncementsCount,
followRequestsCount,
) => {
if (!mergedConfig.showExtraNotifications) {
return 0
}
return [
mergedConfig.showChatsInExtraNotifications ? unreadChatsCount : 0,
mergedConfig.showAnnouncementsInExtraNotifications
? unreadAnnouncementsCount
: 0,
mergedConfig.showFollowRequestsInExtraNotifications
? followRequestsCount
: 0,
].reduce((a, c) => a + c, 0)
}
diff --git a/test/unit/specs/services/matcher/matcher.spec.js b/test/unit/specs/services/matcher/matcher.spec.js
index 753e374335..837a63cb47 100644
--- a/test/unit/specs/services/matcher/matcher.spec.js
+++ b/test/unit/specs/services/matcher/matcher.spec.js
@@ -1,88 +1,88 @@
import * as MatcherService from 'src/services/matcher/matcher.service.js'
const localAttn = () => ({
id: 123,
is_local: true,
name: 'Guy',
screen_name: 'person',
statusnet_profile_url: 'https://instance.com/users/person',
})
const externalAttn = () => ({
id: 123,
is_local: false,
name: 'Guy',
screen_name: 'person@instance.com',
statusnet_profile_url: 'https://instance.com/users/person',
})
describe('MatcherService', () => {
describe('mentionMatchesUrl', () => {
it('should match local mention', () => {
const attention = localAttn()
const url = 'https://instance.com/users/person'
expect(MatcherService.mentionMatchesUrl(attention, url)).to.eql(true)
})
it('should not match a local mention with same name but different instance', () => {
const attention = localAttn()
const url = 'https://website.com/users/person'
expect(MatcherService.mentionMatchesUrl(attention, url)).to.eql(false)
})
it('should match external pleroma mention', () => {
const attention = externalAttn()
const url = 'https://instance.com/users/person'
expect(MatcherService.mentionMatchesUrl(attention, url)).to.eql(true)
})
it('should not match external pleroma mention with same name but different instance', () => {
const attention = externalAttn()
const url = 'https://website.com/users/person'
expect(MatcherService.mentionMatchesUrl(attention, url)).to.eql(false)
})
it('should match external mastodon mention', () => {
const attention = externalAttn()
const url = 'https://instance.com/@person'
expect(MatcherService.mentionMatchesUrl(attention, url)).to.eql(true)
})
it('should not match external mastodon mention with same name but different instance', () => {
const attention = externalAttn()
const url = 'https://website.com/@person'
expect(MatcherService.mentionMatchesUrl(attention, url)).to.eql(false)
})
})
describe('extractTagFromUrl', () => {
it('should return tag name from valid pleroma url', () => {
const url = 'https://website.com/tag/photo'
expect(MatcherService.extractTagFromUrl(url)).to.eql('photo')
})
it('should return tag name from valid mastodon url', () => {
const url = 'https://website.com/tags/sky'
expect(MatcherService.extractTagFromUrl(url)).to.eql('sky')
})
- it('should not return string but false if invalid url', () => {
+ it('should not return string but null if invalid url', () => {
const url = 'https://website.com/users/sky'
- expect(MatcherService.extractTagFromUrl(url)).to.eql(false)
+ expect(MatcherService.extractTagFromUrl(url)).to.eql(null)
})
it('should return tag name from non-ascii tags', () => {
const url = encodeURI('https://website.com/tag/喵喵喵')
expect(MatcherService.extractTagFromUrl(url)).to.eql('喵喵喵')
})
})
})

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 9, 2:56 PM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784768
Default Alt Text
(20 KB)

Event Timeline