Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85803812
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline
Log In to Comment