@atproto/api 0.10.2 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/bsky-agent.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +86 -53
- package/dist/index.js.map +3 -3
- package/dist/rich-text/util.d.ts +4 -0
- package/dist/util.d.ts +1 -0
- package/package.json +2 -2
- package/src/bsky-agent.ts +97 -74
- package/src/index.ts +2 -0
- package/src/rich-text/detection.ts +13 -5
- package/src/rich-text/util.ts +11 -0
- package/src/util.ts +6 -0
- package/tests/bsky-agent.test.ts +131 -7
- package/tests/rich-text-detection.test.ts +38 -3
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sanitizeMutedWordValue(value: string): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/api",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Client library for atproto and Bluesky",
|
|
6
6
|
"keywords": [
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"common-tags": "^1.8.2",
|
|
30
30
|
"@atproto/lex-cli": "^0.3.1",
|
|
31
|
-
"@atproto/dev-env": "^0.2.
|
|
31
|
+
"@atproto/dev-env": "^0.2.36"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"codegen": "pnpm docgen && node ./scripts/generate-code.mjs && lex gen-api ./src/client ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/*",
|
package/src/bsky-agent.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
BskyThreadViewPreference,
|
|
14
14
|
BskyInterestsPreference,
|
|
15
15
|
} from './types'
|
|
16
|
+
import { sanitizeMutedWordValue } from './util'
|
|
16
17
|
|
|
17
18
|
const FEED_VIEW_PREF_DEFAULTS = {
|
|
18
19
|
hideReplies: false,
|
|
@@ -565,16 +566,108 @@ export class BskyAgent extends AtpAgent {
|
|
|
565
566
|
})
|
|
566
567
|
}
|
|
567
568
|
|
|
568
|
-
async upsertMutedWords(
|
|
569
|
-
await
|
|
569
|
+
async upsertMutedWords(newMutedWords: AppBskyActorDefs.MutedWord[]) {
|
|
570
|
+
await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => {
|
|
571
|
+
let mutedWordsPref = prefs.findLast(
|
|
572
|
+
(pref) =>
|
|
573
|
+
AppBskyActorDefs.isMutedWordsPref(pref) &&
|
|
574
|
+
AppBskyActorDefs.validateMutedWordsPref(pref).success,
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) {
|
|
578
|
+
for (const updatedWord of newMutedWords) {
|
|
579
|
+
let foundMatch = false
|
|
580
|
+
const sanitizedUpdatedValue = sanitizeMutedWordValue(
|
|
581
|
+
updatedWord.value,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
// was trimmed down to an empty string e.g. single `#`
|
|
585
|
+
if (!sanitizedUpdatedValue) continue
|
|
586
|
+
|
|
587
|
+
for (const existingItem of mutedWordsPref.items) {
|
|
588
|
+
if (existingItem.value === sanitizedUpdatedValue) {
|
|
589
|
+
existingItem.targets = Array.from(
|
|
590
|
+
new Set([...existingItem.targets, ...updatedWord.targets]),
|
|
591
|
+
)
|
|
592
|
+
foundMatch = true
|
|
593
|
+
break
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (!foundMatch) {
|
|
598
|
+
mutedWordsPref.items.push({
|
|
599
|
+
...updatedWord,
|
|
600
|
+
value: sanitizedUpdatedValue,
|
|
601
|
+
})
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
// if the pref doesn't exist, create it
|
|
606
|
+
mutedWordsPref = {
|
|
607
|
+
items: newMutedWords.map((w) => ({
|
|
608
|
+
...w,
|
|
609
|
+
value: sanitizeMutedWordValue(w.value),
|
|
610
|
+
})),
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return prefs
|
|
615
|
+
.filter((p) => !AppBskyActorDefs.isMutedWordsPref(p))
|
|
616
|
+
.concat([
|
|
617
|
+
{ ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' },
|
|
618
|
+
])
|
|
619
|
+
})
|
|
570
620
|
}
|
|
571
621
|
|
|
572
622
|
async updateMutedWord(mutedWord: AppBskyActorDefs.MutedWord) {
|
|
573
|
-
await
|
|
623
|
+
await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => {
|
|
624
|
+
let mutedWordsPref = prefs.findLast(
|
|
625
|
+
(pref) =>
|
|
626
|
+
AppBskyActorDefs.isMutedWordsPref(pref) &&
|
|
627
|
+
AppBskyActorDefs.validateMutedWordsPref(pref).success,
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) {
|
|
631
|
+
for (const existingItem of mutedWordsPref.items) {
|
|
632
|
+
if (existingItem.value === mutedWord.value) {
|
|
633
|
+
existingItem.targets = mutedWord.targets
|
|
634
|
+
break
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return prefs
|
|
640
|
+
.filter((p) => !AppBskyActorDefs.isMutedWordsPref(p))
|
|
641
|
+
.concat([
|
|
642
|
+
{ ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' },
|
|
643
|
+
])
|
|
644
|
+
})
|
|
574
645
|
}
|
|
575
646
|
|
|
576
647
|
async removeMutedWord(mutedWord: AppBskyActorDefs.MutedWord) {
|
|
577
|
-
await
|
|
648
|
+
await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => {
|
|
649
|
+
let mutedWordsPref = prefs.findLast(
|
|
650
|
+
(pref) =>
|
|
651
|
+
AppBskyActorDefs.isMutedWordsPref(pref) &&
|
|
652
|
+
AppBskyActorDefs.validateMutedWordsPref(pref).success,
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) {
|
|
656
|
+
for (let i = 0; i < mutedWordsPref.items.length; i++) {
|
|
657
|
+
const existing = mutedWordsPref.items[i]
|
|
658
|
+
if (existing.value === mutedWord.value) {
|
|
659
|
+
mutedWordsPref.items.splice(i, 1)
|
|
660
|
+
break
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return prefs
|
|
666
|
+
.filter((p) => !AppBskyActorDefs.isMutedWordsPref(p))
|
|
667
|
+
.concat([
|
|
668
|
+
{ ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' },
|
|
669
|
+
])
|
|
670
|
+
})
|
|
578
671
|
}
|
|
579
672
|
|
|
580
673
|
async hidePost(postUri: string) {
|
|
@@ -646,76 +739,6 @@ async function updateFeedPreferences(
|
|
|
646
739
|
return res
|
|
647
740
|
}
|
|
648
741
|
|
|
649
|
-
/**
|
|
650
|
-
* A helper specifically for updating muted words preferences
|
|
651
|
-
*/
|
|
652
|
-
async function updateMutedWords(
|
|
653
|
-
agent: BskyAgent,
|
|
654
|
-
mutedWords: AppBskyActorDefs.MutedWord[],
|
|
655
|
-
action: 'upsert' | 'update' | 'remove',
|
|
656
|
-
) {
|
|
657
|
-
const sanitizeMutedWord = (word: AppBskyActorDefs.MutedWord) => ({
|
|
658
|
-
value: word.value.replace(/^#/, ''),
|
|
659
|
-
targets: word.targets,
|
|
660
|
-
})
|
|
661
|
-
|
|
662
|
-
await updatePreferences(agent, (prefs: AppBskyActorDefs.Preferences) => {
|
|
663
|
-
let mutedWordsPref = prefs.findLast(
|
|
664
|
-
(pref) =>
|
|
665
|
-
AppBskyActorDefs.isMutedWordsPref(pref) &&
|
|
666
|
-
AppBskyActorDefs.validateMutedWordsPref(pref).success,
|
|
667
|
-
)
|
|
668
|
-
|
|
669
|
-
if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) {
|
|
670
|
-
if (action === 'upsert' || action === 'update') {
|
|
671
|
-
for (const word of mutedWords) {
|
|
672
|
-
let foundMatch = false
|
|
673
|
-
|
|
674
|
-
for (const existingItem of mutedWordsPref.items) {
|
|
675
|
-
if (existingItem.value === sanitizeMutedWord(word).value) {
|
|
676
|
-
existingItem.targets =
|
|
677
|
-
action === 'upsert'
|
|
678
|
-
? Array.from(
|
|
679
|
-
new Set([...existingItem.targets, ...word.targets]),
|
|
680
|
-
)
|
|
681
|
-
: word.targets
|
|
682
|
-
foundMatch = true
|
|
683
|
-
break
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
if (action === 'upsert' && !foundMatch) {
|
|
688
|
-
mutedWordsPref.items.push(sanitizeMutedWord(word))
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
} else if (action === 'remove') {
|
|
692
|
-
for (const word of mutedWords) {
|
|
693
|
-
for (let i = 0; i < mutedWordsPref.items.length; i++) {
|
|
694
|
-
const existing = mutedWordsPref.items[i]
|
|
695
|
-
if (existing.value === sanitizeMutedWord(word).value) {
|
|
696
|
-
mutedWordsPref.items.splice(i, 1)
|
|
697
|
-
break
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
} else {
|
|
703
|
-
// if the pref doesn't exist, create it
|
|
704
|
-
if (action === 'upsert') {
|
|
705
|
-
mutedWordsPref = {
|
|
706
|
-
items: mutedWords.map(sanitizeMutedWord),
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
return prefs
|
|
712
|
-
.filter((p) => !AppBskyActorDefs.isMutedWordsPref(p))
|
|
713
|
-
.concat([
|
|
714
|
-
{ ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' },
|
|
715
|
-
])
|
|
716
|
-
})
|
|
717
|
-
}
|
|
718
|
-
|
|
719
742
|
async function updateHiddenPost(
|
|
720
743
|
agent: BskyAgent,
|
|
721
744
|
postUri: string,
|
package/src/index.ts
CHANGED
|
@@ -8,11 +8,13 @@ export {
|
|
|
8
8
|
} from '@atproto/lexicon'
|
|
9
9
|
export { parseLanguage } from '@atproto/common-web'
|
|
10
10
|
export * from './types'
|
|
11
|
+
export * from './util'
|
|
11
12
|
export * from './client'
|
|
12
13
|
export * from './agent'
|
|
13
14
|
export * from './rich-text/rich-text'
|
|
14
15
|
export * from './rich-text/sanitization'
|
|
15
16
|
export * from './rich-text/unicode'
|
|
17
|
+
export * from './rich-text/util'
|
|
16
18
|
export * from './moderation'
|
|
17
19
|
export * from './moderation/types'
|
|
18
20
|
export { LABELS } from './moderation/const/labels'
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import TLDs from 'tlds'
|
|
2
2
|
import { AppBskyRichtextFacet } from '../client'
|
|
3
3
|
import { UnicodeString } from './unicode'
|
|
4
|
+
import {
|
|
5
|
+
URL_REGEX,
|
|
6
|
+
MENTION_REGEX,
|
|
7
|
+
TAG_REGEX,
|
|
8
|
+
TRAILING_PUNCTUATION_REGEX,
|
|
9
|
+
} from './util'
|
|
4
10
|
|
|
5
11
|
export type Facet = AppBskyRichtextFacet.Main
|
|
6
12
|
|
|
@@ -9,7 +15,7 @@ export function detectFacets(text: UnicodeString): Facet[] | undefined {
|
|
|
9
15
|
const facets: Facet[] = []
|
|
10
16
|
{
|
|
11
17
|
// mentions
|
|
12
|
-
const re =
|
|
18
|
+
const re = MENTION_REGEX
|
|
13
19
|
while ((match = re.exec(text.utf16))) {
|
|
14
20
|
if (!isValidDomain(match[3]) && !match[3].endsWith('.test')) {
|
|
15
21
|
continue // probably not a handle
|
|
@@ -33,8 +39,7 @@ export function detectFacets(text: UnicodeString): Facet[] | undefined {
|
|
|
33
39
|
}
|
|
34
40
|
{
|
|
35
41
|
// links
|
|
36
|
-
const re =
|
|
37
|
-
/(^|\s|\()((https?:\/\/[\S]+)|((?<domain>[a-z][a-z0-9]*(\.[a-z0-9]+)+)[\S]*))/gim
|
|
42
|
+
const re = URL_REGEX
|
|
38
43
|
while ((match = re.exec(text.utf16))) {
|
|
39
44
|
let uri = match[2]
|
|
40
45
|
if (!uri.startsWith('http')) {
|
|
@@ -70,11 +75,14 @@ export function detectFacets(text: UnicodeString): Facet[] | undefined {
|
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
{
|
|
73
|
-
const re =
|
|
78
|
+
const re = TAG_REGEX
|
|
74
79
|
while ((match = re.exec(text.utf16))) {
|
|
75
80
|
let [, leading, tag] = match
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
if (!tag) continue
|
|
83
|
+
|
|
84
|
+
// strip ending punctuation and any spaces
|
|
85
|
+
tag = tag.trim().replace(TRAILING_PUNCTUATION_REGEX, '')
|
|
78
86
|
|
|
79
87
|
if (tag.length === 0 || tag.length > 64) continue
|
|
80
88
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const MENTION_REGEX = /(^|\s|\()(@)([a-zA-Z0-9.-]+)(\b)/g
|
|
2
|
+
export const URL_REGEX =
|
|
3
|
+
/(^|\s|\()((https?:\/\/[\S]+)|((?<domain>[a-z][a-z0-9]*(\.[a-z0-9]+)+)[\S]*))/gim
|
|
4
|
+
export const TRAILING_PUNCTUATION_REGEX = /\p{P}+$/gu
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `\ufe0f` emoji modifier
|
|
8
|
+
* `\u00AD\u2060\u200A\u200B\u200C\u200D\u20e2` zero-width spaces (likely incomplete)
|
|
9
|
+
*/
|
|
10
|
+
export const TAG_REGEX =
|
|
11
|
+
/(^|\s)[##]((?!\ufe0f)[^\s\u00AD\u2060\u200A\u200B\u200C\u200D\u20e2]*[^\d\s\p{P}\u00AD\u2060\u200A\u200B\u200C\u200D\u20e2]+[^\s\u00AD\u2060\u200A\u200B\u200C\u200D\u20e2]*)?/gu
|
package/src/util.ts
ADDED
package/tests/bsky-agent.test.ts
CHANGED
|
@@ -1202,13 +1202,18 @@ describe('agent', () => {
|
|
|
1202
1202
|
await agent.upsertMutedWords([
|
|
1203
1203
|
{ value: 'hashtag', targets: ['content'] },
|
|
1204
1204
|
])
|
|
1205
|
+
// is sanitized to `hashtag`
|
|
1205
1206
|
await agent.upsertMutedWords([{ value: '#hashtag', targets: ['tag'] }])
|
|
1207
|
+
|
|
1206
1208
|
const { mutedWords } = await agent.getPreferences()
|
|
1209
|
+
|
|
1207
1210
|
expect(mutedWords.find((m) => m.value === '#hashtag')).toBeFalsy()
|
|
1211
|
+
// merged with existing
|
|
1208
1212
|
expect(mutedWords.find((m) => m.value === 'hashtag')).toStrictEqual({
|
|
1209
1213
|
value: 'hashtag',
|
|
1210
1214
|
targets: ['content', 'tag'],
|
|
1211
1215
|
})
|
|
1216
|
+
// only one added
|
|
1212
1217
|
expect(mutedWords.filter((m) => m.value === 'hashtag').length).toBe(1)
|
|
1213
1218
|
})
|
|
1214
1219
|
|
|
@@ -1237,15 +1242,21 @@ describe('agent', () => {
|
|
|
1237
1242
|
expect(mutedWords.find((m) => m.value === 'no_exist')).toBeFalsy()
|
|
1238
1243
|
})
|
|
1239
1244
|
|
|
1240
|
-
it('updateMutedWord with
|
|
1245
|
+
it('updateMutedWord with #, does not update', async () => {
|
|
1246
|
+
await agent.upsertMutedWords([
|
|
1247
|
+
{
|
|
1248
|
+
value: '#just_a_tag',
|
|
1249
|
+
targets: ['tag'],
|
|
1250
|
+
},
|
|
1251
|
+
])
|
|
1241
1252
|
await agent.updateMutedWord({
|
|
1242
|
-
value: '
|
|
1253
|
+
value: '#just_a_tag',
|
|
1243
1254
|
targets: ['tag', 'content'],
|
|
1244
1255
|
})
|
|
1245
1256
|
const { mutedWords } = await agent.getPreferences()
|
|
1246
|
-
expect(mutedWords.find((m) => m.value === '
|
|
1247
|
-
value: '
|
|
1248
|
-
targets: ['tag'
|
|
1257
|
+
expect(mutedWords.find((m) => m.value === 'just_a_tag')).toStrictEqual({
|
|
1258
|
+
value: 'just_a_tag',
|
|
1259
|
+
targets: ['tag'],
|
|
1249
1260
|
})
|
|
1250
1261
|
})
|
|
1251
1262
|
|
|
@@ -1262,11 +1273,124 @@ describe('agent', () => {
|
|
|
1262
1273
|
expect(mutedWords.find((m) => m.value === 'tag_then_none')).toBeFalsy()
|
|
1263
1274
|
})
|
|
1264
1275
|
|
|
1265
|
-
it('removeMutedWord with
|
|
1276
|
+
it('removeMutedWord with #, no match, no removal', async () => {
|
|
1266
1277
|
await agent.removeMutedWord({ value: '#hashtag', targets: [] })
|
|
1267
1278
|
const { mutedWords } = await agent.getPreferences()
|
|
1268
1279
|
|
|
1269
|
-
|
|
1280
|
+
// was inserted with #hashtag, but we don't sanitize on remove
|
|
1281
|
+
expect(mutedWords.find((m) => m.value === 'hashtag')).toBeTruthy()
|
|
1282
|
+
})
|
|
1283
|
+
|
|
1284
|
+
it('single-hash #', async () => {
|
|
1285
|
+
const prev = await agent.getPreferences()
|
|
1286
|
+
const length = prev.mutedWords.length
|
|
1287
|
+
await agent.upsertMutedWords([{ value: '#', targets: [] }])
|
|
1288
|
+
const end = await agent.getPreferences()
|
|
1289
|
+
|
|
1290
|
+
// sanitized to empty string, not inserted
|
|
1291
|
+
expect(end.mutedWords.length).toEqual(length)
|
|
1292
|
+
})
|
|
1293
|
+
|
|
1294
|
+
it('multi-hash ##', async () => {
|
|
1295
|
+
await agent.upsertMutedWords([{ value: '##', targets: [] }])
|
|
1296
|
+
const { mutedWords } = await agent.getPreferences()
|
|
1297
|
+
|
|
1298
|
+
expect(mutedWords.find((m) => m.value === '#')).toBeTruthy()
|
|
1299
|
+
})
|
|
1300
|
+
|
|
1301
|
+
it('multi-hash ##hashtag', async () => {
|
|
1302
|
+
await agent.upsertMutedWords([{ value: '##hashtag', targets: [] }])
|
|
1303
|
+
const a = await agent.getPreferences()
|
|
1304
|
+
|
|
1305
|
+
expect(a.mutedWords.find((w) => w.value === '#hashtag')).toBeTruthy()
|
|
1306
|
+
|
|
1307
|
+
await agent.removeMutedWord({ value: '#hashtag', targets: [] })
|
|
1308
|
+
const b = await agent.getPreferences()
|
|
1309
|
+
|
|
1310
|
+
expect(b.mutedWords.find((w) => w.value === '#hashtag')).toBeFalsy()
|
|
1311
|
+
})
|
|
1312
|
+
|
|
1313
|
+
it('hash emoji #️⃣', async () => {
|
|
1314
|
+
await agent.upsertMutedWords([{ value: '#️⃣', targets: [] }])
|
|
1315
|
+
const { mutedWords } = await agent.getPreferences()
|
|
1316
|
+
|
|
1317
|
+
expect(mutedWords.find((m) => m.value === '#️⃣')).toBeTruthy()
|
|
1318
|
+
|
|
1319
|
+
await agent.removeMutedWord({ value: '#️⃣', targets: [] })
|
|
1320
|
+
const end = await agent.getPreferences()
|
|
1321
|
+
|
|
1322
|
+
expect(end.mutedWords.find((m) => m.value === '#️⃣')).toBeFalsy()
|
|
1323
|
+
})
|
|
1324
|
+
|
|
1325
|
+
it('hash emoji ##️⃣', async () => {
|
|
1326
|
+
await agent.upsertMutedWords([{ value: '##️⃣', targets: [] }])
|
|
1327
|
+
const { mutedWords } = await agent.getPreferences()
|
|
1328
|
+
|
|
1329
|
+
expect(mutedWords.find((m) => m.value === '#️⃣')).toBeTruthy()
|
|
1330
|
+
|
|
1331
|
+
await agent.removeMutedWord({ value: '#️⃣', targets: [] })
|
|
1332
|
+
const end = await agent.getPreferences()
|
|
1333
|
+
|
|
1334
|
+
expect(end.mutedWords.find((m) => m.value === '#️⃣')).toBeFalsy()
|
|
1335
|
+
})
|
|
1336
|
+
|
|
1337
|
+
it('hash emoji ###️⃣', async () => {
|
|
1338
|
+
await agent.upsertMutedWords([{ value: '###️⃣', targets: [] }])
|
|
1339
|
+
const { mutedWords } = await agent.getPreferences()
|
|
1340
|
+
|
|
1341
|
+
expect(mutedWords.find((m) => m.value === '##️⃣')).toBeTruthy()
|
|
1342
|
+
|
|
1343
|
+
await agent.removeMutedWord({ value: '##️⃣', targets: [] })
|
|
1344
|
+
const end = await agent.getPreferences()
|
|
1345
|
+
|
|
1346
|
+
expect(end.mutedWords.find((m) => m.value === '##️⃣')).toBeFalsy()
|
|
1347
|
+
})
|
|
1348
|
+
|
|
1349
|
+
describe(`invalid characters`, () => {
|
|
1350
|
+
it('zero width space', async () => {
|
|
1351
|
+
const prev = await agent.getPreferences()
|
|
1352
|
+
const length = prev.mutedWords.length
|
|
1353
|
+
await agent.upsertMutedWords([{ value: '#', targets: [] }])
|
|
1354
|
+
const { mutedWords } = await agent.getPreferences()
|
|
1355
|
+
|
|
1356
|
+
expect(mutedWords.length).toEqual(length)
|
|
1357
|
+
})
|
|
1358
|
+
|
|
1359
|
+
it('newline', async () => {
|
|
1360
|
+
await agent.upsertMutedWords([
|
|
1361
|
+
{ value: 'test value\n with newline', targets: [] },
|
|
1362
|
+
])
|
|
1363
|
+
const { mutedWords } = await agent.getPreferences()
|
|
1364
|
+
|
|
1365
|
+
expect(
|
|
1366
|
+
mutedWords.find((m) => m.value === 'test value with newline'),
|
|
1367
|
+
).toBeTruthy()
|
|
1368
|
+
})
|
|
1369
|
+
|
|
1370
|
+
it('newline(s)', async () => {
|
|
1371
|
+
await agent.upsertMutedWords([
|
|
1372
|
+
{ value: 'test value\n\r with newline', targets: [] },
|
|
1373
|
+
])
|
|
1374
|
+
const { mutedWords } = await agent.getPreferences()
|
|
1375
|
+
|
|
1376
|
+
expect(
|
|
1377
|
+
mutedWords.find((m) => m.value === 'test value with newline'),
|
|
1378
|
+
).toBeTruthy()
|
|
1379
|
+
})
|
|
1380
|
+
|
|
1381
|
+
it('empty space', async () => {
|
|
1382
|
+
await agent.upsertMutedWords([{ value: ' ', targets: [] }])
|
|
1383
|
+
const { mutedWords } = await agent.getPreferences()
|
|
1384
|
+
|
|
1385
|
+
expect(mutedWords.find((m) => m.value === ' ')).toBeFalsy()
|
|
1386
|
+
})
|
|
1387
|
+
|
|
1388
|
+
it('leading/trailing space', async () => {
|
|
1389
|
+
await agent.upsertMutedWords([{ value: ' trim ', targets: [] }])
|
|
1390
|
+
const { mutedWords } = await agent.getPreferences()
|
|
1391
|
+
|
|
1392
|
+
expect(mutedWords.find((m) => m.value === 'trim')).toBeTruthy()
|
|
1393
|
+
})
|
|
1270
1394
|
})
|
|
1271
1395
|
})
|
|
1272
1396
|
|
|
@@ -218,7 +218,7 @@ describe('detectFacets', () => {
|
|
|
218
218
|
}
|
|
219
219
|
})
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
describe('correctly detects tags inline', () => {
|
|
222
222
|
const inputs: [
|
|
223
223
|
string,
|
|
224
224
|
string[],
|
|
@@ -234,11 +234,13 @@ describe('detectFacets', () => {
|
|
|
234
234
|
],
|
|
235
235
|
],
|
|
236
236
|
['#1', [], []],
|
|
237
|
+
['#1a', ['1a'], [{ byteStart: 0, byteEnd: 3 }]],
|
|
237
238
|
['#tag', ['tag'], [{ byteStart: 0, byteEnd: 4 }]],
|
|
238
239
|
['body #tag', ['tag'], [{ byteStart: 5, byteEnd: 9 }]],
|
|
239
240
|
['#tag body', ['tag'], [{ byteStart: 0, byteEnd: 4 }]],
|
|
240
241
|
['body #tag body', ['tag'], [{ byteStart: 5, byteEnd: 9 }]],
|
|
241
242
|
['body #1', [], []],
|
|
243
|
+
['body #1a', ['1a'], [{ byteStart: 5, byteEnd: 8 }]],
|
|
242
244
|
['body #a1', ['a1'], [{ byteStart: 5, byteEnd: 8 }]],
|
|
243
245
|
['#', [], []],
|
|
244
246
|
['#?', [], []],
|
|
@@ -254,12 +256,18 @@ describe('detectFacets', () => {
|
|
|
254
256
|
[],
|
|
255
257
|
[],
|
|
256
258
|
],
|
|
259
|
+
[
|
|
260
|
+
'body #thisisa64characterstring_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!',
|
|
261
|
+
['thisisa64characterstring_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'],
|
|
262
|
+
[{ byteStart: 5, byteEnd: 70 }],
|
|
263
|
+
],
|
|
257
264
|
[
|
|
258
265
|
'its a #double#rainbow',
|
|
259
266
|
['double#rainbow'],
|
|
260
267
|
[{ byteStart: 6, byteEnd: 21 }],
|
|
261
268
|
],
|
|
262
269
|
['##hashash', ['#hashash'], [{ byteStart: 0, byteEnd: 9 }]],
|
|
270
|
+
['##', [], []],
|
|
263
271
|
['some #n0n3s@n5e!', ['n0n3s@n5e'], [{ byteStart: 5, byteEnd: 15 }]],
|
|
264
272
|
[
|
|
265
273
|
'works #with,punctuation',
|
|
@@ -309,9 +317,36 @@ describe('detectFacets', () => {
|
|
|
309
317
|
},
|
|
310
318
|
],
|
|
311
319
|
],
|
|
320
|
+
[
|
|
321
|
+
'this #t\nag should be a tag',
|
|
322
|
+
['t'],
|
|
323
|
+
[
|
|
324
|
+
{
|
|
325
|
+
byteStart: 5,
|
|
326
|
+
byteEnd: 7,
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
],
|
|
330
|
+
['no match (\\u200B): #', [], []],
|
|
331
|
+
['no match (\\u200Ba): #a', [], []],
|
|
332
|
+
['match (a\\u200Bb): #ab', ['a'], [{ byteStart: 18, byteEnd: 20 }]],
|
|
333
|
+
['match (ab\\u200B): #ab', ['ab'], [{ byteStart: 18, byteEnd: 21 }]],
|
|
334
|
+
['no match (\\u20e2tag): #⃢tag', [], []],
|
|
335
|
+
['no match (a\\u20e2b): #a⃢b', ['a'], [{ byteStart: 21, byteEnd: 23 }]],
|
|
336
|
+
[
|
|
337
|
+
'match full width number sign (tag): #tag',
|
|
338
|
+
['tag'],
|
|
339
|
+
[{ byteStart: 36, byteEnd: 42 }],
|
|
340
|
+
],
|
|
341
|
+
[
|
|
342
|
+
'match full width number sign (tag): ##️⃣tag',
|
|
343
|
+
['#️⃣tag'],
|
|
344
|
+
[{ byteStart: 36, byteEnd: 49 }],
|
|
345
|
+
],
|
|
346
|
+
['no match 1?: #1?', [], []],
|
|
312
347
|
]
|
|
313
348
|
|
|
314
|
-
|
|
349
|
+
it.each(inputs)('%s', async (input, tags, indices) => {
|
|
315
350
|
const rt = new RichText({ text: input })
|
|
316
351
|
await rt.detectFacets(agent)
|
|
317
352
|
|
|
@@ -330,7 +365,7 @@ describe('detectFacets', () => {
|
|
|
330
365
|
|
|
331
366
|
expect(detectedTags).toEqual(tags)
|
|
332
367
|
expect(detectedIndices).toEqual(indices)
|
|
333
|
-
}
|
|
368
|
+
})
|
|
334
369
|
})
|
|
335
370
|
})
|
|
336
371
|
|