diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index edf2356c..4f76f667 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -96,17 +96,11 @@ jobs:
with:
distribution: 'zulu'
java-version: '11'
- - name: -- Step 4 -- Use Expo action
- uses: expo/expo-github-action@v7
- with:
- expo-version: latest
- eas-version: latest
- token: ${{ secrets.EXPO_TOKEN }}
- - name: -- Step 5 -- Install node dependencies
+ - name: -- Step 4 -- Install node dependencies
run: yarn install
- - name: -- Step 6 -- Install ruby dependencies
+ - name: -- Step 5 -- Install ruby dependencies
run: bundle install
- - name: -- Step 7 -- Run fastlane
+ - name: -- Step 6 -- Run fastlane
env:
ENVIRONMENT: ${{ steps.branch.outputs.branch }}
LC_ALL: en_US.UTF-8
@@ -118,9 +112,5 @@ jobs:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_KEYSTORE_ALIAS }}
ANDROID_KEYSTORE_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_KEY_PASSWORD }}
- GH_PAT_GET_RELEASE: ${{ secrets.GITHUB_TOKEN }}
FL_GITHUB_RELEASE_API_BEARER: ${{ secrets.GITHUB_TOKEN }}
run: yarn app:build release
- - name: -- Step 8 -- Publish expo update
- run: eas update --auto
-
diff --git a/VERSIONING.md b/VERSIONING.md
deleted file mode 100644
index 7a500bd4..00000000
--- a/VERSIONING.md
+++ /dev/null
@@ -1,24 +0,0 @@
-## Major releases - App Store
-
-"Major releases" are artifacts published as `x.?.?`:
- * An artifact must be released as `x.?.?` if native modules have been changed or updated, including upgrading Expo SDK version.
- * A new app store version has to be submitted.
- * Outdated versions in principle do not receive further OTA updates.
-
-## Minor releases - App Store
-
-"Minor releases" are artifacts published as `?.y.?`:
- * An artifact can be released as `?.y.?` when there is no change nor update made to the native modules.
- * A new app store version can be submitted for better first launch experience.
- * All these versions that are not part of above mentioned outdates versions receive also OTA updates.
-
-## Patch releases - OTA
-
-"Patch releases" are artifacts published as `?.?.z`:
- * An artifact must be release as `?.?.z` when there is no major change to the functionalities.
- * No new app store version will be submitted.
- * All these versions that are not part of above mentioned outdates versions receive also OTA updates.
-
-## OTA release channels
-
- * `MAJOR.MINOR-environment`. Environments include `release`, `candidate` and `development`.
diff --git a/android/app/build.gradle b/android/app/build.gradle
index fb8b1377..7908f03c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -170,10 +170,6 @@ android {
}
}
}
- manifestPlaceholders = [
- runtimeVersion: project.hasProperty('runtimeVersion') ? project.property('runtimeVersion') : "",
- branch: project.hasProperty('branch') ? project.property('branch') : "development"
- ]
}
if (isNewArchitectureEnabled()) {
// We configure the NDK build only if you decide to opt-in for the New Architecture.
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d6209e89..85094f05 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -13,12 +13,9 @@
-
-
-
-
-
-
+
+
+
diff --git a/android/app/src/main/java/com/xmflsct/app/tooot/MainApplication.java b/android/app/src/main/java/com/xmflsct/app/tooot/MainApplication.java
index 97c73b7a..df6bdbcf 100644
--- a/android/app/src/main/java/com/xmflsct/app/tooot/MainApplication.java
+++ b/android/app/src/main/java/com/xmflsct/app/tooot/MainApplication.java
@@ -16,7 +16,6 @@ import com.facebook.soloader.SoLoader;
import expo.modules.ApplicationLifecycleDispatcher;
import expo.modules.ReactNativeHostWrapper;
-import expo.modules.updates.UpdatesController;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
diff --git a/app.config.ts b/app.config.ts
index 53adc405..d7c2413b 100644
--- a/app.config.ts
+++ b/app.config.ts
@@ -1,21 +1,15 @@
import { ExpoConfig } from '@expo/config'
-import { versions } from './package.json'
+import { version } from './package.json'
import 'dotenv/config'
-const toootVersion = `${versions.major}.${versions.minor}.${versions.patch}`
-
export default (): ExpoConfig => ({
- updates: {
- url: "https://u.expo.dev/3288313f-3ff0-496a-a5a9-d8985e7cad5f"
- },
- runtimeVersion: `${versions.major}.${versions.minor}`,
name: 'tooot',
description: 'tooot for Mastodon',
slug: 'tooot',
scheme: 'tooot',
- version: toootVersion,
+ version,
+ extra: { environment: process.env.ENVIRONMENT },
privacy: 'hidden',
- assetBundlePatterns: ['assets/*'],
hooks: {
postPublish: [
{
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 9791512c..232384bd 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -1,19 +1,10 @@
skip_docs
-VERSIONS = read_json( json_path: "./package.json" )[:versions]
+VERSION = read_json( json_path: "./package.json" )[:version]
+GITHUB_RELEASE = "v#{VERSION}"
ENVIRONMENT = ENV["ENVIRONMENT"]
-VERSION = "#{VERSIONS[:major]}.#{VERSIONS[:minor]}"
-RELEASE_CHANNEL = "#{VERSIONS[:major]}.#{VERSIONS[:minor]}-#{ENVIRONMENT}"
BUILD_NUMBER = "#{Time.now.strftime("%y%m%d")}#{ENV["GITHUB_RUN_NUMBER"]}"
GITHUB_REPO = "tooot-app/app"
-case ENVIRONMENT
-when "candidate"
- GITHUB_RELEASE = "v#{VERSION}-#{VERSIONS[:patch]}"
-when "release"
- GITHUB_RELEASE = "v#{VERSION}"
-else
- GITHUB_RELEASE= ""
-end
XCODEPROJ = "./ios/tooot.xcodeproj"
INFO_PLIST = "./ios/tooot/Info.plist"
@@ -31,9 +22,6 @@ private_lane :build_ios do
IPA_FILE = "#{BUILD_DIRECTORY}/tooot.ipa"
DSYM_FILE = "#{BUILD_DIRECTORY}/tooot.app.dSYM.zip"
- set_info_plist_value( path: EXPO_PLIST, key: "EXUpdatesRuntimeVersion", value: VERSION )
- set_info_plist_value( path: EXPO_PLIST, key: "EXUpdatesRequestHeaders", subkey: "expo-channel-name", value: ENVIRONMENT )
-
setup_ci
set_info_plist_value( path: INFO_PLIST, key: "CFBundleShortVersionString", value: VERSION )
set_info_plist_value(
@@ -97,8 +85,6 @@ private_lane :build_android do
print_command: true,
print_command_output: true,
properties: {
- "runtimeVersion" => VERSION,
- "branch" => ENVIRONMENT,
"android.injected.signing.store.file" => "#{File.expand_path('..', Dir.pwd)}/android/tooot.jks",
"android.injected.signing.store.password" => ENV["ANDROID_KEYSTORE_PASSWORD"],
"android.injected.signing.key.alias" => ENV["ANDROID_KEYSTORE_ALIAS"],
@@ -136,8 +122,6 @@ private_lane :build_android_apk do
print_command: true,
print_command_output: true,
properties: {
- "runtimeVersion" => VERSION,
- "branch" => ENVIRONMENT,
"android.injected.signing.store.file" => "#{File.expand_path('..', Dir.pwd)}/android/tooot.jks",
"android.injected.signing.store.password" => ENV["ANDROID_KEYSTORE_PASSWORD"],
"android.injected.signing.key.alias" => ENV["ANDROID_KEYSTORE_ALIAS"],
@@ -149,31 +133,18 @@ private_lane :build_android_apk do
end
lane :ios do
- releaseExists = get_github_release(url: GITHUB_REPO, version: "v#{VERSION}", api_bearer: ENV['GH_PAT_GET_RELEASE'])
- if releaseExists
- puts("Release #{GITHUB_RELEASE} exists. Continue with building React Native only.")
- else
- puts("Release #{GITHUB_RELEASE} does not exist.")
- cocoapods(clean_install: true, podfile: "./ios/Podfile")
- build_ios
- end
+ cocoapods(clean_install: true, podfile: "./ios/Podfile")
+ build_ios
rocket
end
lane :android do
- releaseExists = get_github_release(url: GITHUB_REPO, version: "v#{VERSION}", api_bearer: ENV['GH_PAT_GET_RELEASE'])
- if releaseExists
- puts("Release #{GITHUB_RELEASE} exists. Continue with building React Native only.")
- else
- puts("Release #{GITHUB_RELEASE} does not exist. Create new release as well as new native build.")
- build_android
- end
+ build_android
rocket
end
lane :release do
- releaseExists = get_github_release(url: GITHUB_REPO, version: "v#{VERSION}", api_bearer: ENV['GH_PAT_GET_RELEASE'])
- if !releaseExists
+ if ENVIRONMENT == 'release'
build_android_apk
set_github_release(
repository_name: GITHUB_REPO,
@@ -181,7 +152,7 @@ lane :release do
tag_name: GITHUB_RELEASE,
description: "No changelog provided",
commitish: git_branch,
- is_prerelease: ENVIRONMENT == 'candidate',
+ is_prerelease: false,
upload_assets: ["#{File.expand_path('..', Dir.pwd)}/tooot-#{GITHUB_RELEASE}.apk"]
)
end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index b2d85efc..28466906 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -59,7 +59,7 @@ PODS:
- EXStoreReview (5.3.0):
- ExpoModulesCore
- EXStructuredHeaders (2.2.1)
- - EXUpdates (0.14.4):
+ - EXUpdates (0.14.5):
- ASN1Decoder (~> 1.8)
- EASClient
- EXManifests
@@ -141,24 +141,24 @@ PODS:
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30910.0, >= 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
- - GoogleUtilities/AppDelegateSwizzler (7.7.0):
+ - GoogleUtilities/AppDelegateSwizzler (7.8.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- - GoogleUtilities/Environment (7.7.0):
+ - GoogleUtilities/Environment (7.8.0):
- PromisesObjC (< 3.0, >= 1.2)
- - GoogleUtilities/Logger (7.7.0):
+ - GoogleUtilities/Logger (7.8.0):
- GoogleUtilities/Environment
- - GoogleUtilities/MethodSwizzler (7.7.0):
+ - GoogleUtilities/MethodSwizzler (7.8.0):
- GoogleUtilities/Logger
- - GoogleUtilities/Network (7.7.0):
+ - GoogleUtilities/Network (7.8.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- - "GoogleUtilities/NSData+zlib (7.7.0)"
- - GoogleUtilities/Reachability (7.7.0):
+ - "GoogleUtilities/NSData+zlib (7.8.0)"
+ - GoogleUtilities/Reachability (7.8.0):
- GoogleUtilities/Logger
- - GoogleUtilities/UserDefaults (7.7.0):
+ - GoogleUtilities/UserDefaults (7.8.0):
- GoogleUtilities/Logger
- hermes-engine (0.69.4)
- libevent (2.1.12)
@@ -533,7 +533,7 @@ PODS:
- React-Core
- RNFastImage (8.5.11):
- React-Core
- - SDWebImage (~> 5.13.2)
+ - SDWebImage (~> 5.13.3)
- SDWebImageWebPCoder (~> 0.9.0)
- RNGestureHandler (2.5.0):
- React-Core
@@ -574,10 +574,10 @@ PODS:
- React
- RNSVG (13.0.0):
- React-Core
- - SDWebImage (5.13.2):
- - SDWebImage/Core (= 5.13.2)
- - SDWebImage/Core (5.13.2)
- - SDWebImageWebPCoder (0.9.0):
+ - SDWebImage (5.13.3):
+ - SDWebImage/Core (= 5.13.3)
+ - SDWebImage/Core (5.13.3)
+ - SDWebImageWebPCoder (0.9.1):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.13)
- Sentry (7.23.0):
@@ -892,7 +892,7 @@ SPEC CHECKSUMS:
EXSplashScreen: 31ab6df6d23e97e074d1330224741979943f1d82
EXStoreReview: cbb6b2202bb6f831cd3234d9d8b995cec0eb32f2
EXStructuredHeaders: 5d86829469399370a9fc7cb1e4391b09de87681d
- EXUpdates: 784c8c593f6649d0640e81cede66613703e3c267
+ EXUpdates: 664f999db423f90f3fb3d91edcda9bc28cb5a5b4
EXUpdatesInterface: 2bbc11815dfa2ec3fc02e5534c7592c6b42b5327
EXVideoThumbnails: 486533e1a66c9859f9b9e3b2e1f9f0b275515b48
FBLazyVector: c71b8c429a8af2aff1013934a7152e9d9d0c937d
@@ -906,7 +906,7 @@ SPEC CHECKSUMS:
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
GoogleAppMeasurement: 71156240babd3cc6ced03e0d54816f01a880c730
GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f
- GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1
+ GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7
hermes-engine: 761a544537e62df2a37189389b9d2654dc1f75af
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
@@ -952,15 +952,15 @@ SPEC CHECKSUMS:
ReactCommon: 8f67bd7e0a6afade0f20718f859dc8c2275f2e83
RNCAsyncStorage: b2489b49e38c85e10ed45a888d13a2a4c7b32ea1
RNCClipboard: f1736c75ab85b627a4d57587edb4b60999c4dd80
- RNFastImage: 8e9b5b9e6df94d2e359c0a75a4745ad1311506fd
+ RNFastImage: d2efd2a7c06f15141b2c4116f7c0f14ee429f458
RNGestureHandler: bad495418bcbd3ab47017a38d93d290ebd406f50
RNReanimated: 2cf7451318bb9cc430abeec8d67693f9cf4e039c
RNScreens: ee31ecdf23fe81e93c74feaa086cf173d758ab58
RNSentry: 7495ba091f09f12902d8cf916024efd99b058efe
RNShareMenu: cb9dac548c8bf147d06f0bf07296ad51ea9f5fc3
RNSVG: 42a0c731b11179ebbd27a3eeeafa7201ebb476ff
- SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
- SDWebImageWebPCoder: 3dc350894112feab5375cfba9ce0986544a66a69
+ SDWebImage: af5bbffef2cde09f148d826f9733dcde1a9414cd
+ SDWebImageWebPCoder: 18503de6621dd2c420d680e33d46bf8e1d5169b0
Sentry: a0d4563fa4ddacba31fdcc35daaa8573d87224d6
Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
Yoga: ff994563b2fd98c982ca58e8cd9db2cdaf4dda74
diff --git a/ios/tooot/Supporting/Expo.plist b/ios/tooot/Supporting/Expo.plist
index 27a3a226..b4c81f20 100644
--- a/ios/tooot/Supporting/Expo.plist
+++ b/ios/tooot/Supporting/Expo.plist
@@ -2,20 +2,11 @@
- EXUpdatesCheckOnLaunch
- WIFI_ONLY
+ EXUpdatesAutoSetup
+
EXUpdatesEnabled
-
- EXUpdatesLaunchWaitMs
- 0
- EXUpdatesRequestHeaders
-
- expo-channel-name
- development
-
- EXUpdatesRuntimeVersion
- 0
- EXUpdatesURL
- https://u.expo.dev/3288313f-3ff0-496a-a5a9-d8985e7cad5f
+
+ EXUpdatesSDKVersion
+ 46.0.0
\ No newline at end of file
diff --git a/metro.config.js b/metro.config.js
index 22366d05..6b6f2944 100644
--- a/metro.config.js
+++ b/metro.config.js
@@ -1,6 +1,3 @@
module.exports = {
- transformer: {
- assetPlugins: ['expo-asset/tools/hashAssetFiles'],
- inlineRequires: true
- }
+ transformer: { inlineRequires: true }
}
diff --git a/package.json b/package.json
index 4281b154..717f85a2 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,6 @@
{
"name": "tooot",
- "versions": {
- "major": 4,
- "minor": 3,
- "patch": 1
- },
+ "version": "4.3.2",
"description": "tooot app for Mastodon",
"author": "xmflsct ",
"license": "GPL-3.0-or-later",
@@ -62,11 +58,12 @@
"expo-secure-store": "^11.3.0",
"expo-splash-screen": "^0.16.1",
"expo-store-review": "^5.3.0",
- "expo-updates": "^0.14.4",
+ "expo-updates": "^0.14.5",
"expo-video-thumbnails": "^6.4.0",
"expo-web-browser": "^11.0.0",
"i18next": "^21.9.1",
"li": "^1.3.0",
+ "linkify-it": "^4.0.1",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -107,6 +104,7 @@
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@expo/config": "^7.0.1",
+ "@types/linkify-it": "^3.0.2",
"@types/lodash": "^4.14.184",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
diff --git a/patches/react-native-fast-image+8.5.11.patch b/patches/react-native-fast-image+8.5.11.patch
index a2347146..1fdc8dc2 100644
--- a/patches/react-native-fast-image+8.5.11.patch
+++ b/patches/react-native-fast-image+8.5.11.patch
@@ -1,5 +1,5 @@
diff --git a/node_modules/react-native-fast-image/RNFastImage.podspec b/node_modules/react-native-fast-image/RNFastImage.podspec
-index db0fada..9379119 100644
+index db0fada..a869641 100644
--- a/node_modules/react-native-fast-image/RNFastImage.podspec
+++ b/node_modules/react-native-fast-image/RNFastImage.podspec
@@ -16,6 +16,6 @@ Pod::Spec.new do |s|
@@ -8,7 +8,7 @@ index db0fada..9379119 100644
s.dependency 'React-Core'
- s.dependency 'SDWebImage', '~> 5.11.1'
- s.dependency 'SDWebImageWebPCoder', '~> 0.8.4'
-+ s.dependency 'SDWebImage', '~> 5.13.2'
++ s.dependency 'SDWebImage', '~> 5.13.3'
+ s.dependency 'SDWebImageWebPCoder', '~> 0.9.0'
end
diff --git a/node_modules/react-native-fast-image/android/build.gradle b/node_modules/react-native-fast-image/android/build.gradle
diff --git a/src/App.tsx b/src/App.tsx
index 6419e708..788008db 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -16,7 +16,6 @@ import {
getSettingsLanguage
} from '@utils/slices/settingsSlice'
import ThemeManager from '@utils/styles/ThemeManager'
-import 'expo-asset'
import * as SplashScreen from 'expo-splash-screen'
import React, { useCallback, useEffect, useState } from 'react'
import { LogBox, Platform } from 'react-native'
diff --git a/src/Screens.tsx b/src/Screens.tsx
index 07a072be..4bd00d8c 100644
--- a/src/Screens.tsx
+++ b/src/Screens.tsx
@@ -251,7 +251,6 @@ const Screens: React.FC = ({ localCorrupt }) => {
if (!text && !media.length) {
return
} else {
- console.log('share', text, media)
if (instances.length > 1) {
navigationRef.navigate('Screen-AccountSelection', {
share: { text, media }
diff --git a/src/components/Emojis.tsx b/src/components/Emojis.tsx
index 044dce6d..755d2b84 100644
--- a/src/components/Emojis.tsx
+++ b/src/components/Emojis.tsx
@@ -4,19 +4,13 @@ import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { useEmojisQuery } from '@utils/queryHooks/emojis'
import { getInstanceFrequentEmojis } from '@utils/slices/instancesSlice'
import { chunk, forEach, groupBy, sortBy } from 'lodash'
-import React, {
- Dispatch,
- MutableRefObject,
- PropsWithChildren,
- SetStateAction,
- useCallback,
- useEffect,
- useReducer
-} from 'react'
+import React, { createRef, PropsWithChildren, useEffect, useReducer, useState } from 'react'
import { useTranslation } from 'react-i18next'
+import { Keyboard, KeyboardAvoidingView, View } from 'react-native'
import FastImage from 'react-native-fast-image'
+import { Edge, SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import { useSelector } from 'react-redux'
-import EmojisContext, { emojisReducer } from './Emojis/helpers/EmojisContext'
+import EmojisContext, { Emojis, emojisReducer, EmojisState } from './Emojis/helpers/EmojisContext'
const prefetchEmojis = (
sortedEmojis: {
@@ -45,81 +39,37 @@ const prefetchEmojis = (
} catch {}
}
-export interface Props {
- enabled?: boolean
- value?: string
- setValue:
- | Dispatch>
- | Dispatch>
- selectionRange: MutableRefObject<{
- start: number
- end: number
- }>
- maxLength?: number
+export type Props = {
+ inputProps: EmojisState['inputProps']
+ customButton?: boolean
+ customEdges?: Edge[]
+ customBehavior?: 'height' | 'padding' | 'position'
}
+export const emojis: Emojis = createRef()
+
const ComponentEmojis: React.FC = ({
- enabled = false,
- value,
- setValue,
- selectionRange,
- maxLength,
- children
+ children,
+ inputProps,
+ customButton = false,
+ customEdges = ['bottom'],
+ customBehavior
}) => {
const { reduceMotionEnabled } = useAccessibility()
- const [emojisState, emojisDispatch] = useReducer(emojisReducer, {
- enabled,
- active: false,
- emojis: [],
- shortcode: null
- })
-
+ const [emojisState, emojisDispatch] = useReducer(emojisReducer, { inputProps, targetIndex: -1 })
useEffect(() => {
- if (emojisState.shortcode) {
- addEmoji(emojisState.shortcode)
- emojisDispatch({
- type: 'shortcode',
- payload: null
- })
- }
- }, [emojisState.shortcode])
-
- const addEmoji = useCallback(
- (emojiShortcode: string) => {
- if (value?.length) {
- const contentFront = value.slice(0, selectionRange.current?.start)
- const contentRear = value.slice(selectionRange.current?.end)
-
- const whiteSpaceRear = /\s/g.test(contentRear.slice(-1))
-
- const newTextWithSpace = ` ${emojiShortcode}${
- whiteSpaceRear ? '' : ' '
- }`
- setValue(
- [contentFront, newTextWithSpace, contentRear]
- .join('')
- .slice(0, maxLength)
- )
- } else {
- setValue(`${emojiShortcode} `.slice(0, maxLength))
- }
- },
- [value, selectionRange.current?.start, selectionRange.current?.end]
- )
+ emojisDispatch({ type: 'input', payload: inputProps })
+ }, [inputProps])
const { t } = useTranslation()
- const { data } = useEmojisQuery({ options: { enabled } })
+ const { data } = useEmojisQuery({})
const frequentEmojis = useSelector(getInstanceFrequentEmojis, () => true)
useEffect(() => {
if (data && data.length) {
- let sortedEmojis: {
- title: string
- data: Pick[][]
- }[] = []
- forEach(
- groupBy(sortBy(data, ['category', 'shortcode']), 'category'),
- (value, key) => sortedEmojis.push({ title: key, data: chunk(value, 5) })
+ let sortedEmojis: NonNullable = []
+ forEach(groupBy(sortBy(data, ['category', 'shortcode']), 'category'), (value, key) =>
+ sortedEmojis.push({ title: key, data: chunk(value, 5) })
)
if (frequentEmojis.length) {
sortedEmojis.unshift({
@@ -127,22 +77,60 @@ const ComponentEmojis: React.FC = ({
data: chunk(
frequentEmojis.map(e => e.emoji),
5
- )
+ ),
+ type: 'frequent'
})
}
- emojisDispatch({
- type: 'load',
- payload: sortedEmojis
- })
+ emojis.current = sortedEmojis
prefetchEmojis(sortedEmojis, reduceMotionEnabled)
}
}, [data, reduceMotionEnabled])
+ const insets = useSafeAreaInsets()
+ const [keyboardShown, setKeyboardShown] = useState(false)
+ useEffect(() => {
+ const showSubscription = Keyboard.addListener('keyboardWillShow', () => {
+ const anyInputHasFocus = inputProps.filter(props => props.isFocused.current).length
+ if (anyInputHasFocus) {
+ emojisDispatch({ type: 'target', payload: -1 })
+ }
+ setKeyboardShown(true)
+ })
+ const hideSubscription = Keyboard.addListener('keyboardWillHide', () => {
+ setKeyboardShown(false)
+ })
+
+ return () => {
+ showSubscription.remove()
+ hideSubscription.remove()
+ }
+ }, [inputProps])
+
return (
-
+
+
+
+
+ {children}
+
+ ) : customButton ? null : (
+
+ )
+ }
+ />
+
+
+
+
)
}
diff --git a/src/components/Emojis/Button.tsx b/src/components/Emojis/Button.tsx
index 92ce4620..c7783541 100644
--- a/src/components/Emojis/Button.tsx
+++ b/src/components/Emojis/Button.tsx
@@ -1,50 +1,54 @@
+import { emojis } from '@components/Emojis'
import Icon from '@components/Icon'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext } from 'react'
-import { Pressable, StyleSheet } from 'react-native'
+import { Keyboard, Pressable, View } from 'react-native'
import EmojisContext from './helpers/EmojisContext'
-const EmojisButton = React.memo(
- () => {
- const { colors } = useTheme()
- const { emojisState, emojisDispatch } = useContext(EmojisContext)
+const EmojisButton: React.FC = () => {
+ const { colors } = useTheme()
+ const { emojisState, emojisDispatch } = useContext(EmojisContext)
- return emojisState.enabled ? (
-
- emojisDispatch({ type: 'activate', payload: !emojisState.active })
+ const focusedPropsIndex = emojisState.inputProps?.findIndex(props => props.isFocused.current)
+ if (focusedPropsIndex === -1) {
+ return null
+ }
+
+ return (
+ {
+ if (emojisState.targetIndex === -1) {
+ Keyboard.dismiss()
}
- hitSlop={StyleConstants.Spacing.S}
- style={styles.base}
- children={
+ emojisDispatch({ type: 'target', payload: focusedPropsIndex })
+ }}
+ hitSlop={StyleConstants.Spacing.S}
+ style={{
+ alignSelf: 'flex-end',
+ padding: StyleConstants.Spacing.Global.PagePadding / 2
+ }}
+ children={
+
- }
- />
- ) : null
- },
- () => true
-)
-
-const styles = StyleSheet.create({
- base: {
- paddingLeft: StyleConstants.Spacing.S
- }
-})
+
+ }
+ />
+ )
+}
export default EmojisButton
diff --git a/src/components/Emojis/List.tsx b/src/components/Emojis/List.tsx
index 32a2a931..0ccb2c55 100644
--- a/src/components/Emojis/List.tsx
+++ b/src/components/Emojis/List.tsx
@@ -1,3 +1,5 @@
+import { emojis } from '@components/Emojis'
+import Icon from '@components/Icon'
import CustomText from '@components/Text'
import { useAppDispatch } from '@root/store'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
@@ -5,115 +7,215 @@ import { countInstanceEmoji } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager'
-import React, { useCallback, useContext, useEffect, useRef } from 'react'
+import { chunk } from 'lodash'
+import React, { useContext, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
AccessibilityInfo,
findNodeHandle,
Pressable,
SectionList,
+ TextInput,
View
} from 'react-native'
import FastImage from 'react-native-fast-image'
import validUrl from 'valid-url'
import EmojisContext from './helpers/EmojisContext'
-const EmojisList = React.memo(
- () => {
- const dispatch = useAppDispatch()
- const { reduceMotionEnabled } = useAccessibility()
- const { t } = useTranslation()
+const EmojisList = () => {
+ const dispatch = useAppDispatch()
+ const { reduceMotionEnabled } = useAccessibility()
+ const { t } = useTranslation()
- const { emojisState, emojisDispatch } = useContext(EmojisContext)
- const { colors } = useTheme()
+ const { emojisState, emojisDispatch } = useContext(EmojisContext)
+ const { colors, mode } = useTheme()
- const listItem = useCallback(
- ({ index, item }: { item: Mastodon.Emoji[]; index: number }) => {
- return (
-
- {item.map(emoji => {
- const uri = reduceMotionEnabled ? emoji.static_url : emoji.url
- if (validUrl.isHttpsUri(uri)) {
- return (
- {
- emojisDispatch({
- type: 'shortcode',
- payload: `:${emoji.shortcode}:`
- })
- dispatch(countInstanceEmoji(emoji))
- }}
- >
-
-
- )
- } else {
- return null
- }
- })}
-
- )
- },
- []
+ const addEmoji = (shortcode: string) => {
+ if (emojisState.targetIndex === -1) {
+ return
+ }
+
+ const {
+ value: [value, setValue],
+ selection: [selection, setSelection],
+ ref,
+ maxLength
+ } = emojisState.inputProps[emojisState.targetIndex]
+
+ const contentFront = value.slice(0, selection.start)
+ const contentRear = value.slice(selection.end || selection.start)
+
+ const spaceFront = value.length === 0 || /\s/g.test(contentFront.slice(-1)) ? '' : ' '
+ const spaceRear = /\s/g.test(contentRear[0]) ? '' : ' '
+
+ setValue(
+ [contentFront, spaceFront, shortcode, spaceRear, contentRear].join('').slice(0, maxLength)
)
- const listRef = useRef(null)
- useEffect(() => {
- layoutAnimation()
- const tagEmojis = findNodeHandle(listRef.current)
- if (emojisState.active) {
- tagEmojis && AccessibilityInfo.setAccessibilityFocus(tagEmojis)
- }
- }, [emojisState.active])
+ const addedLength = spaceFront.length + shortcode.length + spaceRear.length
+ setSelection({ start: selection.start + addedLength })
+ ref?.current?.setNativeProps({
+ selection: { start: selection.start + addedLength }
+ })
+ }
- return emojisState.active ? (
+ const listItem = ({ index, item }: { item: Mastodon.Emoji[]; index: number }) => {
+ return (
+
+ {item.map(emoji => {
+ const uri = reduceMotionEnabled ? emoji.static_url : emoji.url
+ if (validUrl.isHttpsUri(uri)) {
+ return (
+ {
+ addEmoji(`:${emoji.shortcode}:`)
+ dispatch(countInstanceEmoji(emoji))
+ }}
+ style={{ padding: StyleConstants.Spacing.S }}
+ >
+
+
+ )
+ } else {
+ return null
+ }
+ })}
+
+ )
+ }
+
+ const listRef = useRef(null)
+ useEffect(() => {
+ const tagEmojis = findNodeHandle(listRef.current)
+ if (emojisState.targetIndex !== -1) {
+ layoutAnimation()
+ tagEmojis && AccessibilityInfo.setAccessibilityFocus(tagEmojis)
+ }
+ }, [emojisState.targetIndex])
+
+ const [search, setSearch] = useState('')
+ const searchLength = useRef(0)
+ useEffect(() => {
+ if (
+ (search.length === 0 && searchLength.current === 1) ||
+ (search.length === 1 && searchLength.current === 0)
+ ) {
+ layoutAnimation()
+ }
+ searchLength.current = search.length
+ }, [search.length, searchLength.current])
+
+ return emojisState.targetIndex !== -1 ? (
+
+
+
+
+
+
+ {
+ if (emojisState.targetIndex !== -1) {
+ emojisState.inputProps[emojisState.targetIndex].ref?.current?.focus()
+ }
+ emojisDispatch({ type: 'target', payload: -1 })
+ }}
+ >
+
+
+
item[0].shortcode}
+ sections={
+ search.length
+ ? [
+ {
+ title: 'Search result',
+ data: emojis.current
+ ? chunk(
+ emojis.current
+ .filter(e => e.type !== 'frequent')
+ .flatMap(e =>
+ e.data.flatMap(e => e).filter(emoji => emoji.shortcode.includes(search))
+ ),
+ 2
+ )
+ : []
+ }
+ ]
+ : emojis.current || []
+ }
+ keyExtractor={item => item[0]?.shortcode}
renderSectionHeader={({ section: { title } }) => (
-
+
{title}
)}
renderItem={listItem}
windowSize={4}
+ contentContainerStyle={{
+ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
+ minHeight: 32 * 2 + StyleConstants.Spacing.M * 3
+ }}
/>
- ) : null
- },
- () => true
-)
+
+ ) : null
+}
export default EmojisList
diff --git a/src/components/Emojis/helpers/EmojisContext.tsx b/src/components/Emojis/helpers/EmojisContext.tsx
index 121207de..c7a80e33 100644
--- a/src/components/Emojis/helpers/EmojisContext.tsx
+++ b/src/components/Emojis/helpers/EmojisContext.tsx
@@ -1,28 +1,31 @@
-import { createContext, Dispatch } from 'react'
+import { createContext, Dispatch, MutableRefObject, RefObject } from 'react'
+import { TextInput } from 'react-native'
+
+type inputProps = {
+ value: [string, (value: string) => void]
+ selection: [{ start: number; end?: number }, (selection: { start: number; end?: number }) => void]
+ isFocused: MutableRefObject
+ ref: RefObject // For controlling focus
+ maxLength?: number
+}
+
+export type Emojis = MutableRefObject<
+ | {
+ title: string
+ data: Pick[][]
+ type?: 'frequent'
+ }[]
+ | null
+>
export type EmojisState = {
- enabled: boolean
- active: boolean
- emojis: {
- title: string
- data: Pick[][]
- }[]
- shortcode: Mastodon.Emoji['shortcode'] | null
+ inputProps: inputProps[]
+ targetIndex: number
}
export type EmojisAction =
- | {
- type: 'load'
- payload: NonNullable
- }
- | {
- type: 'activate'
- payload: EmojisState['active']
- }
- | {
- type: 'shortcode'
- payload: EmojisState['shortcode']
- }
+ | { type: 'input'; payload: EmojisState['inputProps'] }
+ | { type: 'target'; payload: EmojisState['targetIndex'] }
type ContextType = {
emojisState: EmojisState
@@ -32,12 +35,10 @@ const EmojisContext = createContext({} as ContextType)
export const emojisReducer = (state: EmojisState, action: EmojisAction) => {
switch (action.type) {
- case 'activate':
- return { ...state, active: action.payload }
- case 'load':
- return { ...state, emojis: action.payload }
- case 'shortcode':
- return { ...state, shortcode: action.payload }
+ case 'input':
+ return { ...state, inputProps: action.payload }
+ case 'target':
+ return { ...state, targetIndex: action.payload }
}
}
diff --git a/src/components/Input.tsx b/src/components/Input.tsx
index 5c3752c2..7354dc44 100644
--- a/src/components/Input.tsx
+++ b/src/components/Input.tsx
@@ -1,99 +1,62 @@
import { StyleConstants } from '@utils/styles/constants'
-import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager'
-import React, {
- Dispatch,
- SetStateAction,
- useEffect,
- useRef,
- useState
-} from 'react'
+import React, { forwardRef, RefObject } from 'react'
import { Platform, TextInput, TextInputProps, View } from 'react-native'
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated'
-import { ComponentEmojis, EmojisButton, EmojisList } from './Emojis'
-import EmojisContext from './Emojis/helpers/EmojisContext'
+import { EmojisState } from './Emojis/helpers/EmojisContext'
import CustomText from './Text'
-export interface Props {
- autoFocus?: boolean
-
+export type Props = {
title?: string
-
multiline?: boolean
-
- emoji?: boolean
-
- value?: string
- setValue:
- | Dispatch>
- | Dispatch>
-
- options?: Omit<
+} & Pick, 'value' | 'selection' | 'isFocused'> &
+ Omit<
TextInputProps,
- | 'autoFocus'
- | 'onFocus'
- | 'onBlur'
| 'style'
| 'onChangeText'
| 'onSelectionChange'
| 'keyboardAppearance'
| 'textAlignVertical'
+ | 'multiline'
+ | 'selection'
+ | 'value'
>
-}
-const Input: React.FC = ({
- autoFocus = true,
- title,
- multiline = false,
- emoji = false,
- value,
- setValue,
- options
-}) => {
- const { colors, mode } = useTheme()
+const ComponentInput = forwardRef(
+ (
+ {
+ title,
+ multiline = false,
+ value: [value, setValue],
+ selection: [selection, setSelection],
+ isFocused,
+ ...props
+ }: Props,
+ ref: RefObject
+ ) => {
+ const { colors, mode } = useTheme()
- const animateTitle = useAnimatedStyle(() => {
- if (value) {
- return {
- fontSize: withTiming(StyleConstants.Font.Size.S),
- paddingHorizontal: withTiming(StyleConstants.Spacing.XS),
- left: withTiming(StyleConstants.Spacing.S),
- top: withTiming(-(StyleConstants.Font.Size.S / 2) - 2),
- backgroundColor: withTiming(colors.backgroundDefault)
- }
- } else {
- return {
- fontSize: withTiming(StyleConstants.Font.Size.M),
- paddingHorizontal: withTiming(0),
- left: withTiming(StyleConstants.Spacing.S),
- top: withTiming(StyleConstants.Spacing.S + 1),
- backgroundColor: withTiming(colors.backgroundDefaultTransparent)
- }
- }
- }, [mode, value])
-
- const selectionRange = useRef<{ start: number; end: number }>(
- value
- ? {
- start: value.length,
- end: value.length
+ const animateTitle = useAnimatedStyle(() => {
+ if (value) {
+ return {
+ fontSize: withTiming(StyleConstants.Font.Size.S),
+ paddingHorizontal: withTiming(StyleConstants.Spacing.XS),
+ left: withTiming(StyleConstants.Spacing.S),
+ top: withTiming(-(StyleConstants.Font.Size.S / 2) - 2),
+ backgroundColor: withTiming(colors.backgroundDefault)
}
- : { start: 0, end: 0 }
- )
+ } else {
+ return {
+ fontSize: withTiming(StyleConstants.Font.Size.M),
+ paddingHorizontal: withTiming(0),
+ left: withTiming(StyleConstants.Spacing.S),
+ top: withTiming(StyleConstants.Spacing.S + 1),
+ backgroundColor: withTiming(colors.backgroundDefaultTransparent)
+ }
+ }
+ }, [mode, value])
- const [inputFocused, setInputFocused] = useState(false)
- useEffect(() => {
- layoutAnimation()
- }, [inputFocused])
-
- return (
-
+ return (
= ({
alignItems: 'stretch'
}}
>
-
- {({ emojisDispatch }) => (
- setInputFocused(true)}
- onBlur={() => {
- setInputFocused(false)
- emojisDispatch({ type: 'activate', payload: false })
- }}
- style={{
- flex: 1,
- fontSize: StyleConstants.Font.Size.M,
- color: colors.primaryDefault,
- minHeight:
- Platform.OS === 'ios' && multiline
- ? StyleConstants.Font.LineHeight.M * 5
- : undefined
- }}
- onChangeText={setValue}
- onSelectionChange={({ nativeEvent: { selection } }) =>
- (selectionRange.current = selection)
- }
- value={value}
- {...(multiline && {
- multiline,
- numberOfLines: Platform.OS === 'android' ? 5 : undefined
- })}
- keyboardAppearance={mode}
- textAlignVertical='top'
- {...options}
- />
- )}
-
+ (isFocused.current = true)}
+ onBlur={() => (isFocused.current = false)}
+ onSelectionChange={({ nativeEvent }) => setSelection(nativeEvent.selection)}
+ {...(multiline && {
+ multiline,
+ numberOfLines: Platform.OS === 'android' ? 5 : undefined
+ })}
+ keyboardAppearance={mode}
+ textAlignVertical='top'
+ {...props}
+ />
+
{title ? (
-
+
{title}
) : null}
+
- {options?.maxLength && value?.length ? (
+ {props?.maxLength && value?.length ? (
= ({
color: colors.secondary
}}
>
- {value?.length} / {options.maxLength}
+ {value?.length} / {props.maxLength}
) : null}
- {inputFocused ? : null}
-
-
- )
-}
+ )
+ }
+)
-export default Input
+export default ComponentInput
diff --git a/src/components/Menu/Row.tsx b/src/components/Menu/Row.tsx
index d6097f0d..e5027ff1 100644
--- a/src/components/Menu/Row.tsx
+++ b/src/components/Menu/Row.tsx
@@ -50,10 +50,7 @@ const MenuRow: React.FC = ({
const loadingSpinkit = useMemo(
() => (
-
+
),
[theme]
@@ -111,11 +108,7 @@ const MenuRow: React.FC = ({
}}
/>
) : null}
-
+
{title}
@@ -127,7 +120,7 @@ const MenuRow: React.FC = ({
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
- marginLeft: StyleConstants.Spacing.M
+ paddingLeft: StyleConstants.Spacing.L
}}
>
{content ? (
diff --git a/src/components/Timeline/Shared/Attachment.tsx b/src/components/Timeline/Shared/Attachment.tsx
index 02857181..ba9fad2a 100644
--- a/src/components/Timeline/Shared/Attachment.tsx
+++ b/src/components/Timeline/Shared/Attachment.tsx
@@ -41,6 +41,7 @@ const TimelineAttachment = React.memo(
}
const [sensitiveShown, setSensitiveShown] = useState(defaultSensitive())
+ // @ts-ignore
const imageUrls: RootStackParamList['Screen-ImagesViewer']['imageUrls'] =
status.media_attachments
.map(attachment => {
diff --git a/src/i18n/de/components/contextMenu.json b/src/i18n/de/components/contextMenu.json
index d83f947b..808a5d0f 100644
--- a/src/i18n/de/components/contextMenu.json
+++ b/src/i18n/de/components/contextMenu.json
@@ -11,7 +11,7 @@
"action_true": ""
},
"reports": {
- "action": "User melden"
+ "action": "Melden und blockieren"
}
},
"copy": {
diff --git a/src/i18n/en/components/contextMenu.json b/src/i18n/en/components/contextMenu.json
index d7ba0c11..6dbd8ea1 100644
--- a/src/i18n/en/components/contextMenu.json
+++ b/src/i18n/en/components/contextMenu.json
@@ -11,7 +11,7 @@
"action_true": "Unblock user"
},
"reports": {
- "action": "Report user"
+ "action": "Report and block"
}
},
"copy": {
diff --git a/src/i18n/it/components/contextMenu.json b/src/i18n/it/components/contextMenu.json
index abfcd532..6f130c39 100644
--- a/src/i18n/it/components/contextMenu.json
+++ b/src/i18n/it/components/contextMenu.json
@@ -11,7 +11,7 @@
"action_true": "Sblocca utente"
},
"reports": {
- "action": "Segnala utente"
+ "action": "Segnala e blocca"
}
},
"copy": {
diff --git a/src/i18n/ja/components/contextMenu.json b/src/i18n/ja/components/contextMenu.json
index 95dbc129..47a35d43 100644
--- a/src/i18n/ja/components/contextMenu.json
+++ b/src/i18n/ja/components/contextMenu.json
@@ -11,7 +11,7 @@
"action_true": "ユーザーのブロックを解除"
},
"reports": {
- "action": "ユーザーを報告"
+ "action": "通報とブロック"
}
},
"copy": {
diff --git a/src/i18n/ko/components/contextMenu.json b/src/i18n/ko/components/contextMenu.json
index 9f88f338..063b227b 100644
--- a/src/i18n/ko/components/contextMenu.json
+++ b/src/i18n/ko/components/contextMenu.json
@@ -11,7 +11,7 @@
"action_true": ""
},
"reports": {
- "action": "사용자 신고"
+ "action": ""
}
},
"copy": {
diff --git a/src/i18n/pt_BR/components/contextMenu.json b/src/i18n/pt_BR/components/contextMenu.json
index 82634dde..0b1c7410 100644
--- a/src/i18n/pt_BR/components/contextMenu.json
+++ b/src/i18n/pt_BR/components/contextMenu.json
@@ -11,7 +11,7 @@
"action_true": "Desbloquear usuário"
},
"reports": {
- "action": "Denunciar usuário"
+ "action": "Denunciar e bloquear"
}
},
"copy": {
diff --git a/src/i18n/vi/components/contextMenu.json b/src/i18n/vi/components/contextMenu.json
index 9e265115..0dee5c99 100644
--- a/src/i18n/vi/components/contextMenu.json
+++ b/src/i18n/vi/components/contextMenu.json
@@ -11,7 +11,7 @@
"action_true": "Bỏ chặn người dùng"
},
"reports": {
- "action": "Báo cáo"
+ "action": "Báo cáo và chặn"
}
},
"copy": {
diff --git a/src/i18n/zh-Hans/components/contextMenu.json b/src/i18n/zh-Hans/components/contextMenu.json
index cd6339a6..a17add00 100644
--- a/src/i18n/zh-Hans/components/contextMenu.json
+++ b/src/i18n/zh-Hans/components/contextMenu.json
@@ -11,7 +11,7 @@
"action_true": "取消屏蔽用户"
},
"reports": {
- "action": "举报用户"
+ "action": "举报并屏蔽"
}
},
"copy": {
diff --git a/src/modules/autolinker/anchor-tag-builder.d.ts b/src/modules/autolinker/anchor-tag-builder.d.ts
deleted file mode 100644
index 09a5965d..00000000
--- a/src/modules/autolinker/anchor-tag-builder.d.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import { Match } from "./match/match";
-import { HtmlTag } from "./html-tag";
-import { TruncateConfigObj } from "./autolinker";
-/**
- * @protected
- * @class Autolinker.AnchorTagBuilder
- * @extends Object
- *
- * Builds anchor (<a>) tags for the Autolinker utility when a match is
- * found.
- *
- * Normally this class is instantiated, configured, and used internally by an
- * {@link Autolinker} instance, but may actually be used indirectly in a
- * {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag}
- * instances which may be modified before returning from the
- * {@link Autolinker#replaceFn replaceFn}. For example:
- *
- * var html = Autolinker.link( "Test google.com", {
- * replaceFn : function( match ) {
- * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance
- * tag.setAttr( 'rel', 'nofollow' );
- *
- * return tag;
- * }
- * } );
- *
- * // generated html:
- * // Test google.com
- */
-export declare class AnchorTagBuilder {
- /**
- * @cfg {Boolean} newWindow
- * @inheritdoc Autolinker#newWindow
- */
- private readonly newWindow;
- /**
- * @cfg {Object} truncate
- * @inheritdoc Autolinker#truncate
- */
- private readonly truncate;
- /**
- * @cfg {String} className
- * @inheritdoc Autolinker#className
- */
- private readonly className;
- /**
- * @method constructor
- * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
- */
- constructor(cfg?: AnchorTagBuilderCfg);
- /**
- * Generates the actual anchor (<a>) tag to use in place of the
- * matched text, via its `match` object.
- *
- * @param {Autolinker.match.Match} match The Match instance to generate an
- * anchor tag from.
- * @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag.
- */
- build(match: Match): HtmlTag;
- /**
- * Creates the Object (map) of the HTML attributes for the anchor (<a>)
- * tag being generated.
- *
- * @protected
- * @param {Autolinker.match.Match} match The Match instance to generate an
- * anchor tag from.
- * @return {Object} A key/value Object (map) of the anchor tag's attributes.
- */
- protected createAttrs(match: Match): {
- [attrName: string]: string;
- };
- /**
- * Creates the CSS class that will be used for a given anchor tag, based on
- * the `matchType` and the {@link #className} config.
- *
- * Example returns:
- *
- * - "" // no {@link #className}
- * - "myLink myLink-url" // url match
- * - "myLink myLink-email" // email match
- * - "myLink myLink-phone" // phone match
- * - "myLink myLink-hashtag" // hashtag match
- * - "myLink myLink-mention myLink-twitter" // mention match with Twitter service
- *
- * @protected
- * @param {Autolinker.match.Match} match The Match instance to generate an
- * anchor tag from.
- * @return {String} The CSS class string for the link. Example return:
- * "myLink myLink-url". If no {@link #className} was configured, returns
- * an empty string.
- */
- protected createCssClass(match: Match): string;
- /**
- * Processes the `anchorText` by truncating the text according to the
- * {@link #truncate} config.
- *
- * @private
- * @param {String} anchorText The anchor tag's text (i.e. what will be
- * displayed).
- * @return {String} The processed `anchorText`.
- */
- private processAnchorText;
- /**
- * Performs the truncation of the `anchorText` based on the {@link #truncate}
- * option. If the `anchorText` is longer than the length specified by the
- * {@link #truncate} option, the truncation is performed based on the
- * `location` property. See {@link #truncate} for details.
- *
- * @private
- * @param {String} anchorText The anchor tag's text (i.e. what will be
- * displayed).
- * @return {String} The truncated anchor text.
- */
- private doTruncate;
-}
-export interface AnchorTagBuilderCfg {
- newWindow?: boolean;
- truncate?: TruncateConfigObj;
- className?: string;
-}
diff --git a/src/modules/autolinker/anchor-tag-builder.js b/src/modules/autolinker/anchor-tag-builder.js
deleted file mode 100644
index b44caaab..00000000
--- a/src/modules/autolinker/anchor-tag-builder.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import { HtmlTag } from "./html-tag";
-import { truncateSmart } from "./truncate/truncate-smart";
-import { truncateMiddle } from "./truncate/truncate-middle";
-import { truncateEnd } from "./truncate/truncate-end";
-/**
- * @protected
- * @class Autolinker.AnchorTagBuilder
- * @extends Object
- *
- * Builds anchor (<a>) tags for the Autolinker utility when a match is
- * found.
- *
- * Normally this class is instantiated, configured, and used internally by an
- * {@link Autolinker} instance, but may actually be used indirectly in a
- * {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag}
- * instances which may be modified before returning from the
- * {@link Autolinker#replaceFn replaceFn}. For example:
- *
- * var html = Autolinker.link( "Test google.com", {
- * replaceFn : function( match ) {
- * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance
- * tag.setAttr( 'rel', 'nofollow' );
- *
- * return tag;
- * }
- * } );
- *
- * // generated html:
- * // Test google.com
- */
-var AnchorTagBuilder = /** @class */ (function () {
- /**
- * @method constructor
- * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
- */
- function AnchorTagBuilder(cfg) {
- if (cfg === void 0) { cfg = {}; }
- /**
- * @cfg {Boolean} newWindow
- * @inheritdoc Autolinker#newWindow
- */
- this.newWindow = false; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Object} truncate
- * @inheritdoc Autolinker#truncate
- */
- this.truncate = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {String} className
- * @inheritdoc Autolinker#className
- */
- this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
- this.newWindow = cfg.newWindow || false;
- this.truncate = cfg.truncate || {};
- this.className = cfg.className || '';
- }
- /**
- * Generates the actual anchor (<a>) tag to use in place of the
- * matched text, via its `match` object.
- *
- * @param {Autolinker.match.Match} match The Match instance to generate an
- * anchor tag from.
- * @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag.
- */
- AnchorTagBuilder.prototype.build = function (match) {
- return new HtmlTag({
- tagName: 'a',
- attrs: this.createAttrs(match),
- innerHtml: this.processAnchorText(match.getAnchorText())
- });
- };
- /**
- * Creates the Object (map) of the HTML attributes for the anchor (<a>)
- * tag being generated.
- *
- * @protected
- * @param {Autolinker.match.Match} match The Match instance to generate an
- * anchor tag from.
- * @return {Object} A key/value Object (map) of the anchor tag's attributes.
- */
- AnchorTagBuilder.prototype.createAttrs = function (match) {
- var attrs = {
- 'href': match.getAnchorHref() // we'll always have the `href` attribute
- };
- var cssClass = this.createCssClass(match);
- if (cssClass) {
- attrs['class'] = cssClass;
- }
- if (this.newWindow) {
- attrs['target'] = "_blank";
- attrs['rel'] = "noopener noreferrer"; // Issue #149. See https://mathiasbynens.github.io/rel-noopener/
- }
- if (this.truncate) {
- if (this.truncate.length && this.truncate.length < match.getAnchorText().length) {
- attrs['title'] = match.getAnchorHref();
- }
- }
- return attrs;
- };
- /**
- * Creates the CSS class that will be used for a given anchor tag, based on
- * the `matchType` and the {@link #className} config.
- *
- * Example returns:
- *
- * - "" // no {@link #className}
- * - "myLink myLink-url" // url match
- * - "myLink myLink-email" // email match
- * - "myLink myLink-phone" // phone match
- * - "myLink myLink-hashtag" // hashtag match
- * - "myLink myLink-mention myLink-twitter" // mention match with Twitter service
- *
- * @protected
- * @param {Autolinker.match.Match} match The Match instance to generate an
- * anchor tag from.
- * @return {String} The CSS class string for the link. Example return:
- * "myLink myLink-url". If no {@link #className} was configured, returns
- * an empty string.
- */
- AnchorTagBuilder.prototype.createCssClass = function (match) {
- var className = this.className;
- if (!className) {
- return "";
- }
- else {
- var returnClasses = [className], cssClassSuffixes = match.getCssClassSuffixes();
- for (var i = 0, len = cssClassSuffixes.length; i < len; i++) {
- returnClasses.push(className + '-' + cssClassSuffixes[i]);
- }
- return returnClasses.join(' ');
- }
- };
- /**
- * Processes the `anchorText` by truncating the text according to the
- * {@link #truncate} config.
- *
- * @private
- * @param {String} anchorText The anchor tag's text (i.e. what will be
- * displayed).
- * @return {String} The processed `anchorText`.
- */
- AnchorTagBuilder.prototype.processAnchorText = function (anchorText) {
- anchorText = this.doTruncate(anchorText);
- return anchorText;
- };
- /**
- * Performs the truncation of the `anchorText` based on the {@link #truncate}
- * option. If the `anchorText` is longer than the length specified by the
- * {@link #truncate} option, the truncation is performed based on the
- * `location` property. See {@link #truncate} for details.
- *
- * @private
- * @param {String} anchorText The anchor tag's text (i.e. what will be
- * displayed).
- * @return {String} The truncated anchor text.
- */
- AnchorTagBuilder.prototype.doTruncate = function (anchorText) {
- var truncate = this.truncate;
- if (!truncate || !truncate.length)
- return anchorText;
- var truncateLength = truncate.length, truncateLocation = truncate.location;
- if (truncateLocation === 'smart') {
- return truncateSmart(anchorText, truncateLength);
- }
- else if (truncateLocation === 'middle') {
- return truncateMiddle(anchorText, truncateLength);
- }
- else {
- return truncateEnd(anchorText, truncateLength);
- }
- };
- return AnchorTagBuilder;
-}());
-export { AnchorTagBuilder };
-
-//# sourceMappingURL=anchor-tag-builder.js.map
diff --git a/src/modules/autolinker/autolinker.d.ts b/src/modules/autolinker/autolinker.d.ts
deleted file mode 100644
index 979a7356..00000000
--- a/src/modules/autolinker/autolinker.d.ts
+++ /dev/null
@@ -1,699 +0,0 @@
-import { AnchorTagBuilder } from "./anchor-tag-builder";
-import { Match } from "./match/match";
-import { EmailMatch } from "./match/email-match";
-import { HashtagMatch } from "./match/hashtag-match";
-import { MentionMatch } from "./match/mention-match";
-import { PhoneMatch } from "./match/phone-match";
-import { UrlMatch } from "./match/url-match";
-import { Matcher } from "./matcher/matcher";
-import { HtmlTag } from "./html-tag";
-import { EmailMatcher } from "./matcher/email-matcher";
-import { UrlMatcher } from "./matcher/url-matcher";
-import { HashtagMatcher } from "./matcher/hashtag-matcher";
-import { PhoneMatcher } from "./matcher/phone-matcher";
-import { MentionMatcher } from "./matcher/mention-matcher";
-/**
- * @class Autolinker
- * @extends Object
- *
- * Utility class used to process a given string of text, and wrap the matches in
- * the appropriate anchor (<a>) tags to turn them into links.
- *
- * Any of the configuration options may be provided in an Object provided
- * to the Autolinker constructor, which will configure how the {@link #link link()}
- * method will process the links.
- *
- * For example:
- *
- * var autolinker = new Autolinker( {
- * newWindow : false,
- * truncate : 30
- * } );
- *
- * var html = autolinker.link( "Joe went to www.yahoo.com" );
- * // produces: 'Joe went to yahoo.com'
- *
- *
- * The {@link #static-link static link()} method may also be used to inline
- * options into a single call, which may be more convenient for one-off uses.
- * For example:
- *
- * var html = Autolinker.link( "Joe went to www.yahoo.com", {
- * newWindow : false,
- * truncate : 30
- * } );
- * // produces: 'Joe went to yahoo.com'
- *
- *
- * ## Custom Replacements of Links
- *
- * If the configuration options do not provide enough flexibility, a {@link #replaceFn}
- * may be provided to fully customize the output of Autolinker. This function is
- * called once for each URL/Email/Phone#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
- * match that is encountered.
- *
- * For example:
- *
- * var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
- *
- * var linkedText = Autolinker.link( input, {
- * replaceFn : function( match ) {
- * console.log( "href = ", match.getAnchorHref() );
- * console.log( "text = ", match.getAnchorText() );
- *
- * switch( match.getType() ) {
- * case 'url' :
- * console.log( "url: ", match.getUrl() );
- *
- * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) {
- * var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes
- * tag.setAttr( 'rel', 'nofollow' );
- * tag.addClass( 'external-link' );
- *
- * return tag;
- *
- * } else {
- * return true; // let Autolinker perform its normal anchor tag replacement
- * }
- *
- * case 'email' :
- * var email = match.getEmail();
- * console.log( "email: ", email );
- *
- * if( email === "my@own.address" ) {
- * return false; // don't auto-link this particular email address; leave as-is
- * } else {
- * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
- * }
- *
- * case 'phone' :
- * var phoneNumber = match.getPhoneNumber();
- * console.log( phoneNumber );
- *
- * return '' + phoneNumber + '';
- *
- * case 'hashtag' :
- * var hashtag = match.getHashtag();
- * console.log( hashtag );
- *
- * return '' + hashtag + '';
- *
- * case 'mention' :
- * var mention = match.getMention();
- * console.log( mention );
- *
- * return '' + mention + '';
- * }
- * }
- * } );
- *
- *
- * The function may return the following values:
- *
- * - `true` (Boolean): Allow Autolinker to replace the match as it normally
- * would.
- * - `false` (Boolean): Do not replace the current match at all - leave as-is.
- * - Any String: If a string is returned from the function, the string will be
- * used directly as the replacement HTML for the match.
- * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify
- * an HTML tag before writing out its HTML text.
- */
-export default class Autolinker {
- /**
- * @static
- * @property {String} version
- *
- * The Autolinker version number in the form major.minor.patch
- *
- * Ex: 0.25.1
- */
- static readonly version = "3.14.1";
- /**
- * For backwards compatibility with Autolinker 1.x, the AnchorTagBuilder
- * class is provided as a static on the Autolinker class.
- */
- static readonly AnchorTagBuilder: typeof AnchorTagBuilder;
- /**
- * For backwards compatibility with Autolinker 1.x, the HtmlTag class is
- * provided as a static on the Autolinker class.
- */
- static readonly HtmlTag: typeof HtmlTag;
- /**
- * For backwards compatibility with Autolinker 1.x, the Matcher classes are
- * provided as statics on the Autolinker class.
- */
- static readonly matcher: {
- Email: typeof EmailMatcher;
- Hashtag: typeof HashtagMatcher;
- Matcher: typeof Matcher;
- Mention: typeof MentionMatcher;
- Phone: typeof PhoneMatcher;
- Url: typeof UrlMatcher;
- };
- /**
- * For backwards compatibility with Autolinker 1.x, the Match classes are
- * provided as statics on the Autolinker class.
- */
- static readonly match: {
- Email: typeof EmailMatch;
- Hashtag: typeof HashtagMatch;
- Match: typeof Match;
- Mention: typeof MentionMatch;
- Phone: typeof PhoneMatch;
- Url: typeof UrlMatch;
- };
- /**
- * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
- * Hashtags, and Mentions found in the given chunk of HTML. Does not link URLs
- * found within HTML tags.
- *
- * For instance, if given the text: `You should go to http://www.yahoo.com`,
- * then the result will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
- *
- * Example:
- *
- * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
- * // Produces: "Go to google.com"
- *
- * @static
- * @param {String} textOrHtml The HTML or text to find matches within (depending
- * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #mention},
- * {@link #hashtag}, and {@link #mention} options are enabled).
- * @param {Object} [options] Any of the configuration options for the Autolinker
- * class, specified in an Object (map). See the class description for an
- * example call.
- * @return {String} The HTML text, with matches automatically linked.
- */
- static link(textOrHtml: string, options?: AutolinkerConfig): string;
- /**
- * Parses the input `textOrHtml` looking for URLs, email addresses, phone
- * numbers, username handles, and hashtags (depending on the configuration
- * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
- * objects describing those matches (without making any replacements).
- *
- * Note that if parsing multiple pieces of text, it is slightly more efficient
- * to create an Autolinker instance, and use the instance-level {@link #parse}
- * method.
- *
- * Example:
- *
- * var matches = Autolinker.parse( "Hello google.com, I am asdf@asdf.com", {
- * urls: true,
- * email: true
- * } );
- *
- * console.log( matches.length ); // 2
- * console.log( matches[ 0 ].getType() ); // 'url'
- * console.log( matches[ 0 ].getUrl() ); // 'google.com'
- * console.log( matches[ 1 ].getType() ); // 'email'
- * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
- *
- * @static
- * @param {String} textOrHtml The HTML or text to find matches within
- * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
- * {@link #hashtag}, and {@link #mention} options are enabled).
- * @param {Object} [options] Any of the configuration options for the Autolinker
- * class, specified in an Object (map). See the class description for an
- * example call.
- * @return {Autolinker.match.Match[]} The array of Matches found in the
- * given input `textOrHtml`.
- */
- static parse(textOrHtml: string, options: AutolinkerConfig): Match[];
- /**
- * The Autolinker version number exposed on the instance itself.
- *
- * Ex: 0.25.1
- */
- readonly version = "3.14.1";
- /**
- * @cfg {Boolean/Object} [urls]
- *
- * `true` if URLs should be automatically linked, `false` if they should not
- * be. Defaults to `true`.
- *
- * Examples:
- *
- * urls: true
- *
- * // or
- *
- * urls: {
- * schemeMatches : true,
- * wwwMatches : true,
- * tldMatches : true
- * }
- *
- * As shown above, this option also accepts an Object form with 3 properties
- * to allow for more customization of what exactly gets linked. All default
- * to `true`:
- *
- * @cfg {Boolean} [urls.schemeMatches] `true` to match URLs found prefixed
- * with a scheme, i.e. `http://google.com`, or `other+scheme://google.com`,
- * `false` to prevent these types of matches.
- * @cfg {Boolean} [urls.wwwMatches] `true` to match urls found prefixed with
- * `'www.'`, i.e. `www.google.com`. `false` to prevent these types of
- * matches. Note that if the URL had a prefixed scheme, and
- * `schemeMatches` is true, it will still be linked.
- * @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
- * level domains (.com, .net, etc.) that are not prefixed with a scheme or
- * `'www.'`. This option attempts to match anything that looks like a URL
- * in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc. `false`
- * to prevent these types of matches.
- */
- private readonly urls;
- /**
- * @cfg {Boolean} [email=true]
- *
- * `true` if email addresses should be automatically linked, `false` if they
- * should not be.
- */
- private readonly email;
- /**
- * @cfg {Boolean} [phone=true]
- *
- * `true` if Phone numbers ("(555)555-5555") should be automatically linked,
- * `false` if they should not be.
- */
- private readonly phone;
- /**
- * @cfg {Boolean/String} [hashtag=false]
- *
- * A string for the service name to have hashtags (ex: "#myHashtag")
- * auto-linked to. The currently-supported values are:
- *
- * - 'twitter'
- * - 'facebook'
- * - 'instagram'
- *
- * Pass `false` to skip auto-linking of hashtags.
- */
- private readonly hashtag;
- /**
- * @cfg {String/Boolean} [mention=false]
- *
- * A string for the service name to have mentions (ex: "@myuser")
- * auto-linked to. The currently supported values are:
- *
- * - 'twitter'
- * - 'instagram'
- * - 'soundcloud'
- *
- * Defaults to `false` to skip auto-linking of mentions.
- */
- private readonly mention;
- /**
- * @cfg {Boolean} [newWindow=true]
- *
- * `true` if the links should open in a new window, `false` otherwise.
- */
- private readonly newWindow;
- /**
- * @cfg {Boolean/Object} [stripPrefix=true]
- *
- * `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
- * from the beginning of URL links' text, `false` otherwise. Defaults to
- * `true`.
- *
- * Examples:
- *
- * stripPrefix: true
- *
- * // or
- *
- * stripPrefix: {
- * scheme : true,
- * www : true
- * }
- *
- * As shown above, this option also accepts an Object form with 2 properties
- * to allow for more customization of what exactly is prevented from being
- * displayed. Both default to `true`:
- *
- * @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
- * a URL match from being displayed to the user. Example:
- * `'http://google.com'` will be displayed as `'google.com'`. `false` to
- * not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
- * will be removed, so as not to remove a potentially dangerous scheme
- * (such as `'file://'` or `'javascript:'`)
- * @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
- * `'www.'` part of a URL match from being displayed to the user. Ex:
- * `'www.google.com'` will be displayed as `'google.com'`. `false` to not
- * strip the `'www'`.
- */
- private readonly stripPrefix;
- /**
- * @cfg {Boolean} [stripTrailingSlash=true]
- *
- * `true` to remove the trailing slash from URL matches, `false` to keep
- * the trailing slash.
- *
- * Example when `true`: `http://google.com/` will be displayed as
- * `http://google.com`.
- */
- private readonly stripTrailingSlash;
- /**
- * @cfg {Boolean} [decodePercentEncoding=true]
- *
- * `true` to decode percent-encoded characters in URL matches, `false` to keep
- * the percent-encoded characters.
- *
- * Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
- * be displayed as `https://en.wikipedia.org/wiki/San_José`.
- */
- private readonly decodePercentEncoding;
- /**
- * @cfg {Number/Object} [truncate=0]
- *
- * ## Number Form
- *
- * A number for how many characters matched text should be truncated to
- * inside the text of a link. If the matched text is over this number of
- * characters, it will be truncated to this length by adding a two period
- * ellipsis ('..') to the end of the string.
- *
- * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file'
- * truncated to 25 characters might look something like this:
- * 'yahoo.com/some/long/pat..'
- *
- * Example Usage:
- *
- * truncate: 25
- *
- *
- * Defaults to `0` for "no truncation."
- *
- *
- * ## Object Form
- *
- * An Object may also be provided with two properties: `length` (Number) and
- * `location` (String). `location` may be one of the following: 'end'
- * (default), 'middle', or 'smart'.
- *
- * Example Usage:
- *
- * truncate: { length: 25, location: 'middle' }
- *
- * @cfg {Number} [truncate.length=0] How many characters to allow before
- * truncation will occur. Defaults to `0` for "no truncation."
- * @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
- *
- * - 'end' (default): will truncate up to the number of characters, and then
- * add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
- * - 'middle': will truncate and add the ellipsis in the middle. Ex:
- * 'yahoo.com/s..th/to/a/file'
- * - 'smart': for URLs where the algorithm attempts to strip out unnecessary
- * parts first (such as the 'www.', then URL scheme, hash, etc.),
- * attempting to make the URL human-readable before looking for a good
- * point to insert the ellipsis if it is still too long. Ex:
- * 'yahoo.com/some..to/a/file'. For more details, see
- * {@link Autolinker.truncate.TruncateSmart}.
- */
- private readonly truncate;
- /**
- * @cfg {String} className
- *
- * A CSS class name to add to the generated links. This class will be added
- * to all links, as well as this class plus match suffixes for styling
- * url/email/phone/hashtag/mention links differently.
- *
- * For example, if this config is provided as "myLink", then:
- *
- * - URL links will have the CSS classes: "myLink myLink-url"
- * - Email links will have the CSS classes: "myLink myLink-email", and
- * - Phone links will have the CSS classes: "myLink myLink-phone"
- * - Hashtag links will have the CSS classes: "myLink myLink-hashtag"
- * - Mention links will have the CSS classes: "myLink myLink-mention myLink-[type]"
- * where [type] is either "instagram", "twitter" or "soundcloud"
- */
- private readonly className;
- /**
- * @cfg {Function} replaceFn
- *
- * A function to individually process each match found in the input string.
- *
- * See the class's description for usage.
- *
- * The `replaceFn` can be called with a different context object (`this`
- * reference) using the {@link #context} cfg.
- *
- * This function is called with the following parameter:
- *
- * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which
- * can be used to retrieve information about the match that the `replaceFn`
- * is currently processing. See {@link Autolinker.match.Match} subclasses
- * for details.
- */
- private readonly replaceFn;
- /**
- * @cfg {Object} context
- *
- * The context object (`this` reference) to call the `replaceFn` with.
- *
- * Defaults to this Autolinker instance.
- */
- private readonly context;
- /**
- * @cfg {Boolean} [sanitizeHtml=false]
- *
- * `true` to HTML-encode the start and end brackets of existing HTML tags found
- * in the input string. This will escape `<` and `>` characters to `<` and
- * `>`, respectively.
- *
- * Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
- * but will remove the significance of existing HTML tags in the input string. If
- * you would like to maintain the significance of existing HTML tags while also
- * making the output HTML string safe, leave this option as `false` and use a
- * tool like https://github.com/cure53/DOMPurify (or others) on the input string
- * before running Autolinker.
- */
- private readonly sanitizeHtml;
- /**
- * @private
- * @property {Autolinker.matcher.Matcher[]} matchers
- *
- * The {@link Autolinker.matcher.Matcher} instances for this Autolinker
- * instance.
- *
- * This is lazily created in {@link #getMatchers}.
- */
- private matchers;
- /**
- * @private
- * @property {Autolinker.AnchorTagBuilder} tagBuilder
- *
- * The AnchorTagBuilder instance used to build match replacement anchor tags.
- * Note: this is lazily instantiated in the {@link #getTagBuilder} method.
- */
- private tagBuilder;
- /**
- * @method constructor
- * @param {Object} [cfg] The configuration options for the Autolinker instance,
- * specified in an Object (map).
- */
- constructor(cfg?: AutolinkerConfig);
- /**
- * Normalizes the {@link #urls} config into an Object with 3 properties:
- * `schemeMatches`, `wwwMatches`, and `tldMatches`, all Booleans.
- *
- * See {@link #urls} config for details.
- *
- * @private
- * @param {Boolean/Object} urls
- * @return {Object}
- */
- private normalizeUrlsCfg;
- /**
- * Normalizes the {@link #stripPrefix} config into an Object with 2
- * properties: `scheme`, and `www` - both Booleans.
- *
- * See {@link #stripPrefix} config for details.
- *
- * @private
- * @param {Boolean/Object} stripPrefix
- * @return {Object}
- */
- private normalizeStripPrefixCfg;
- /**
- * Normalizes the {@link #truncate} config into an Object with 2 properties:
- * `length` (Number), and `location` (String).
- *
- * See {@link #truncate} config for details.
- *
- * @private
- * @param {Number/Object} truncate
- * @return {Object}
- */
- private normalizeTruncateCfg;
- /**
- * Parses the input `textOrHtml` looking for URLs, email addresses, phone
- * numbers, username handles, and hashtags (depending on the configuration
- * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
- * objects describing those matches (without making any replacements).
- *
- * This method is used by the {@link #link} method, but can also be used to
- * simply do parsing of the input in order to discover what kinds of links
- * there are and how many.
- *
- * Example usage:
- *
- * var autolinker = new Autolinker( {
- * urls: true,
- * email: true
- * } );
- *
- * var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
- *
- * console.log( matches.length ); // 2
- * console.log( matches[ 0 ].getType() ); // 'url'
- * console.log( matches[ 0 ].getUrl() ); // 'google.com'
- * console.log( matches[ 1 ].getType() ); // 'email'
- * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
- *
- * @param {String} textOrHtml The HTML or text to find matches within
- * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
- * {@link #hashtag}, and {@link #mention} options are enabled).
- * @return {Autolinker.match.Match[]} The array of Matches found in the
- * given input `textOrHtml`.
- */
- parse(textOrHtml: string): Match[];
- /**
- * After we have found all matches, we need to remove matches that overlap
- * with a previous match. This can happen for instance with URLs, where the
- * url 'google.com/#link' would match '#link' as a hashtag. Because the
- * '#link' part is contained in a larger match that comes before the HashTag
- * match, we'll remove the HashTag match.
- *
- * @private
- * @param {Autolinker.match.Match[]} matches
- * @return {Autolinker.match.Match[]}
- */
- private compactMatches;
- /**
- * Removes matches for matchers that were turned off in the options. For
- * example, if {@link #hashtag hashtags} were not to be matched, we'll
- * remove them from the `matches` array here.
- *
- * Note: we *must* use all Matchers on the input string, and then filter
- * them out later. For example, if the options were `{ url: false, hashtag: true }`,
- * we wouldn't want to match the text '#link' as a HashTag inside of the text
- * 'google.com/#link'. The way the algorithm works is that we match the full
- * URL first (which prevents the accidental HashTag match), and then we'll
- * simply throw away the URL match.
- *
- * @private
- * @param {Autolinker.match.Match[]} matches The array of matches to remove
- * the unwanted matches from. Note: this array is mutated for the
- * removals.
- * @return {Autolinker.match.Match[]} The mutated input `matches` array.
- */
- private removeUnwantedMatches;
- /**
- * Parses the input `text` looking for URLs, email addresses, phone
- * numbers, username handles, and hashtags (depending on the configuration
- * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
- * objects describing those matches.
- *
- * This method processes a **non-HTML string**, and is used to parse and
- * match within the text nodes of an HTML string. This method is used
- * internally by {@link #parse}.
- *
- * @private
- * @param {String} text The text to find matches within (depending on if the
- * {@link #urls}, {@link #email}, {@link #phone},
- * {@link #hashtag}, and {@link #mention} options are enabled). This must be a non-HTML string.
- * @param {Number} [offset=0] The offset of the text node within the
- * original string. This is used when parsing with the {@link #parse}
- * method to generate correct offsets within the {@link Autolinker.match.Match}
- * instances, but may be omitted if calling this method publicly.
- * @return {Autolinker.match.Match[]} The array of Matches found in the
- * given input `text`.
- */
- private parseText;
- /**
- * Automatically links URLs, Email addresses, Phone numbers, Hashtags,
- * and Mentions (Twitter, Instagram, Soundcloud) found in the given chunk of HTML. Does not link
- * URLs found within HTML tags.
- *
- * For instance, if given the text: `You should go to http://www.yahoo.com`,
- * then the result will be `You should go to
- * <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
- *
- * This method finds the text around any HTML elements in the input
- * `textOrHtml`, which will be the text that is processed. Any original HTML
- * elements will be left as-is, as well as the text that is already wrapped
- * in anchor (<a>) tags.
- *
- * @param {String} textOrHtml The HTML or text to autolink matches within
- * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #hashtag}, and {@link #mention} options are enabled).
- * @return {String} The HTML, with matches automatically linked.
- */
- link(textOrHtml: string): string;
- /**
- * Creates the return string value for a given match in the input string.
- *
- * This method handles the {@link #replaceFn}, if one was provided.
- *
- * @private
- * @param {Autolinker.match.Match} match The Match object that represents
- * the match.
- * @return {String} The string that the `match` should be replaced with.
- * This is usually the anchor tag string, but may be the `matchStr` itself
- * if the match is not to be replaced.
- */
- private createMatchReturnVal;
- /**
- * Lazily instantiates and returns the {@link Autolinker.matcher.Matcher}
- * instances for this Autolinker instance.
- *
- * @private
- * @return {Autolinker.matcher.Matcher[]}
- */
- private getMatchers;
- /**
- * Returns the {@link #tagBuilder} instance for this Autolinker instance,
- * lazily instantiating it if it does not yet exist.
- *
- * @private
- * @return {Autolinker.AnchorTagBuilder}
- */
- private getTagBuilder;
-}
-export interface AutolinkerConfig {
- urls?: UrlsConfig;
- email?: boolean;
- phone?: boolean;
- hashtag?: HashtagConfig;
- mention?: MentionConfig;
- newWindow?: boolean;
- stripPrefix?: StripPrefixConfig;
- stripTrailingSlash?: boolean;
- truncate?: TruncateConfig;
- className?: string;
- replaceFn?: ReplaceFn | null;
- context?: any;
- sanitizeHtml?: boolean;
- decodePercentEncoding?: boolean;
-}
-export declare type UrlsConfig = boolean | UrlsConfigObj;
-export interface UrlsConfigObj {
- schemeMatches?: boolean;
- wwwMatches?: boolean;
- tldMatches?: boolean;
-}
-export declare type UrlMatchTypeOptions = 'scheme' | 'www' | 'tld';
-export declare type StripPrefixConfig = boolean | StripPrefixConfigObj;
-export interface StripPrefixConfigObj {
- scheme?: boolean;
- www?: boolean;
-}
-export declare type TruncateConfig = number | TruncateConfigObj;
-export interface TruncateConfigObj {
- length?: number;
- location?: "end" | "middle" | "smart";
-}
-export declare type HashtagConfig = false | HashtagServices;
-export declare type HashtagServices = 'twitter' | 'facebook' | 'instagram';
-export declare type MentionConfig = false | MentionServices;
-export declare type MentionServices = 'mastodon' | 'twitter' | 'instagram' | 'soundcloud';
-export declare type ReplaceFn = (match: Match) => ReplaceFnReturn;
-export declare type ReplaceFnReturn = boolean | string | HtmlTag | null | undefined | void;
diff --git a/src/modules/autolinker/autolinker.js b/src/modules/autolinker/autolinker.js
deleted file mode 100644
index 86ceb9f3..00000000
--- a/src/modules/autolinker/autolinker.js
+++ /dev/null
@@ -1,907 +0,0 @@
-import { defaults, remove, splitAndCapture } from "./utils";
-import { AnchorTagBuilder } from "./anchor-tag-builder";
-import { Match } from "./match/match";
-import { EmailMatch } from "./match/email-match";
-import { HashtagMatch } from "./match/hashtag-match";
-import { MentionMatch } from "./match/mention-match";
-import { PhoneMatch } from "./match/phone-match";
-import { UrlMatch } from "./match/url-match";
-import { Matcher } from "./matcher/matcher";
-import { HtmlTag } from "./html-tag";
-import { EmailMatcher } from "./matcher/email-matcher";
-import { UrlMatcher } from "./matcher/url-matcher";
-import { HashtagMatcher } from "./matcher/hashtag-matcher";
-import { PhoneMatcher } from "./matcher/phone-matcher";
-import { MentionMatcher } from "./matcher/mention-matcher";
-import { parseHtml } from './htmlParser/parse-html';
-/**
- * @class Autolinker
- * @extends Object
- *
- * Utility class used to process a given string of text, and wrap the matches in
- * the appropriate anchor (<a>) tags to turn them into links.
- *
- * Any of the configuration options may be provided in an Object provided
- * to the Autolinker constructor, which will configure how the {@link #link link()}
- * method will process the links.
- *
- * For example:
- *
- * var autolinker = new Autolinker( {
- * newWindow : false,
- * truncate : 30
- * } );
- *
- * var html = autolinker.link( "Joe went to www.yahoo.com" );
- * // produces: 'Joe went to yahoo.com'
- *
- *
- * The {@link #static-link static link()} method may also be used to inline
- * options into a single call, which may be more convenient for one-off uses.
- * For example:
- *
- * var html = Autolinker.link( "Joe went to www.yahoo.com", {
- * newWindow : false,
- * truncate : 30
- * } );
- * // produces: 'Joe went to yahoo.com'
- *
- *
- * ## Custom Replacements of Links
- *
- * If the configuration options do not provide enough flexibility, a {@link #replaceFn}
- * may be provided to fully customize the output of Autolinker. This function is
- * called once for each URL/Email/Phone#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
- * match that is encountered.
- *
- * For example:
- *
- * var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
- *
- * var linkedText = Autolinker.link( input, {
- * replaceFn : function( match ) {
- * console.log( "href = ", match.getAnchorHref() );
- * console.log( "text = ", match.getAnchorText() );
- *
- * switch( match.getType() ) {
- * case 'url' :
- * console.log( "url: ", match.getUrl() );
- *
- * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) {
- * var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes
- * tag.setAttr( 'rel', 'nofollow' );
- * tag.addClass( 'external-link' );
- *
- * return tag;
- *
- * } else {
- * return true; // let Autolinker perform its normal anchor tag replacement
- * }
- *
- * case 'email' :
- * var email = match.getEmail();
- * console.log( "email: ", email );
- *
- * if( email === "my@own.address" ) {
- * return false; // don't auto-link this particular email address; leave as-is
- * } else {
- * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
- * }
- *
- * case 'phone' :
- * var phoneNumber = match.getPhoneNumber();
- * console.log( phoneNumber );
- *
- * return '' + phoneNumber + '';
- *
- * case 'hashtag' :
- * var hashtag = match.getHashtag();
- * console.log( hashtag );
- *
- * return '' + hashtag + '';
- *
- * case 'mention' :
- * var mention = match.getMention();
- * console.log( mention );
- *
- * return '' + mention + '';
- * }
- * }
- * } );
- *
- *
- * The function may return the following values:
- *
- * - `true` (Boolean): Allow Autolinker to replace the match as it normally
- * would.
- * - `false` (Boolean): Do not replace the current match at all - leave as-is.
- * - Any String: If a string is returned from the function, the string will be
- * used directly as the replacement HTML for the match.
- * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify
- * an HTML tag before writing out its HTML text.
- */
-var Autolinker = /** @class */ (function () {
- /**
- * @method constructor
- * @param {Object} [cfg] The configuration options for the Autolinker instance,
- * specified in an Object (map).
- */
- function Autolinker(cfg) {
- if (cfg === void 0) { cfg = {}; }
- /**
- * The Autolinker version number exposed on the instance itself.
- *
- * Ex: 0.25.1
- */
- this.version = Autolinker.version;
- /**
- * @cfg {Boolean/Object} [urls]
- *
- * `true` if URLs should be automatically linked, `false` if they should not
- * be. Defaults to `true`.
- *
- * Examples:
- *
- * urls: true
- *
- * // or
- *
- * urls: {
- * schemeMatches : true,
- * wwwMatches : true,
- * tldMatches : true
- * }
- *
- * As shown above, this option also accepts an Object form with 3 properties
- * to allow for more customization of what exactly gets linked. All default
- * to `true`:
- *
- * @cfg {Boolean} [urls.schemeMatches] `true` to match URLs found prefixed
- * with a scheme, i.e. `http://google.com`, or `other+scheme://google.com`,
- * `false` to prevent these types of matches.
- * @cfg {Boolean} [urls.wwwMatches] `true` to match urls found prefixed with
- * `'www.'`, i.e. `www.google.com`. `false` to prevent these types of
- * matches. Note that if the URL had a prefixed scheme, and
- * `schemeMatches` is true, it will still be linked.
- * @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
- * level domains (.com, .net, etc.) that are not prefixed with a scheme or
- * `'www.'`. This option attempts to match anything that looks like a URL
- * in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc. `false`
- * to prevent these types of matches.
- */
- this.urls = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Boolean} [email=true]
- *
- * `true` if email addresses should be automatically linked, `false` if they
- * should not be.
- */
- this.email = true; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Boolean} [phone=true]
- *
- * `true` if Phone numbers ("(555)555-5555") should be automatically linked,
- * `false` if they should not be.
- */
- this.phone = true; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Boolean/String} [hashtag=false]
- *
- * A string for the service name to have hashtags (ex: "#myHashtag")
- * auto-linked to. The currently-supported values are:
- *
- * - 'twitter'
- * - 'facebook'
- * - 'instagram'
- *
- * Pass `false` to skip auto-linking of hashtags.
- */
- this.hashtag = false; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {String/Boolean} [mention=false]
- *
- * A string for the service name to have mentions (ex: "@myuser")
- * auto-linked to. The currently supported values are:
- *
- * - 'twitter'
- * - 'instagram'
- * - 'soundcloud'
- *
- * Defaults to `false` to skip auto-linking of mentions.
- */
- this.mention = false; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Boolean} [newWindow=true]
- *
- * `true` if the links should open in a new window, `false` otherwise.
- */
- this.newWindow = true; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Boolean/Object} [stripPrefix=true]
- *
- * `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
- * from the beginning of URL links' text, `false` otherwise. Defaults to
- * `true`.
- *
- * Examples:
- *
- * stripPrefix: true
- *
- * // or
- *
- * stripPrefix: {
- * scheme : true,
- * www : true
- * }
- *
- * As shown above, this option also accepts an Object form with 2 properties
- * to allow for more customization of what exactly is prevented from being
- * displayed. Both default to `true`:
- *
- * @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
- * a URL match from being displayed to the user. Example:
- * `'http://google.com'` will be displayed as `'google.com'`. `false` to
- * not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
- * will be removed, so as not to remove a potentially dangerous scheme
- * (such as `'file://'` or `'javascript:'`)
- * @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
- * `'www.'` part of a URL match from being displayed to the user. Ex:
- * `'www.google.com'` will be displayed as `'google.com'`. `false` to not
- * strip the `'www'`.
- */
- this.stripPrefix = { scheme: true, www: true }; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Boolean} [stripTrailingSlash=true]
- *
- * `true` to remove the trailing slash from URL matches, `false` to keep
- * the trailing slash.
- *
- * Example when `true`: `http://google.com/` will be displayed as
- * `http://google.com`.
- */
- this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Boolean} [decodePercentEncoding=true]
- *
- * `true` to decode percent-encoded characters in URL matches, `false` to keep
- * the percent-encoded characters.
- *
- * Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
- * be displayed as `https://en.wikipedia.org/wiki/San_José`.
- */
- this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Number/Object} [truncate=0]
- *
- * ## Number Form
- *
- * A number for how many characters matched text should be truncated to
- * inside the text of a link. If the matched text is over this number of
- * characters, it will be truncated to this length by adding a two period
- * ellipsis ('..') to the end of the string.
- *
- * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file'
- * truncated to 25 characters might look something like this:
- * 'yahoo.com/some/long/pat..'
- *
- * Example Usage:
- *
- * truncate: 25
- *
- *
- * Defaults to `0` for "no truncation."
- *
- *
- * ## Object Form
- *
- * An Object may also be provided with two properties: `length` (Number) and
- * `location` (String). `location` may be one of the following: 'end'
- * (default), 'middle', or 'smart'.
- *
- * Example Usage:
- *
- * truncate: { length: 25, location: 'middle' }
- *
- * @cfg {Number} [truncate.length=0] How many characters to allow before
- * truncation will occur. Defaults to `0` for "no truncation."
- * @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
- *
- * - 'end' (default): will truncate up to the number of characters, and then
- * add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
- * - 'middle': will truncate and add the ellipsis in the middle. Ex:
- * 'yahoo.com/s..th/to/a/file'
- * - 'smart': for URLs where the algorithm attempts to strip out unnecessary
- * parts first (such as the 'www.', then URL scheme, hash, etc.),
- * attempting to make the URL human-readable before looking for a good
- * point to insert the ellipsis if it is still too long. Ex:
- * 'yahoo.com/some..to/a/file'. For more details, see
- * {@link Autolinker.truncate.TruncateSmart}.
- */
- this.truncate = { length: 0, location: 'end' }; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {String} className
- *
- * A CSS class name to add to the generated links. This class will be added
- * to all links, as well as this class plus match suffixes for styling
- * url/email/phone/hashtag/mention links differently.
- *
- * For example, if this config is provided as "myLink", then:
- *
- * - URL links will have the CSS classes: "myLink myLink-url"
- * - Email links will have the CSS classes: "myLink myLink-email", and
- * - Phone links will have the CSS classes: "myLink myLink-phone"
- * - Hashtag links will have the CSS classes: "myLink myLink-hashtag"
- * - Mention links will have the CSS classes: "myLink myLink-mention myLink-[type]"
- * where [type] is either "instagram", "twitter" or "soundcloud"
- */
- this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Function} replaceFn
- *
- * A function to individually process each match found in the input string.
- *
- * See the class's description for usage.
- *
- * The `replaceFn` can be called with a different context object (`this`
- * reference) using the {@link #context} cfg.
- *
- * This function is called with the following parameter:
- *
- * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which
- * can be used to retrieve information about the match that the `replaceFn`
- * is currently processing. See {@link Autolinker.match.Match} subclasses
- * for details.
- */
- this.replaceFn = null; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Object} context
- *
- * The context object (`this` reference) to call the `replaceFn` with.
- *
- * Defaults to this Autolinker instance.
- */
- this.context = undefined; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @cfg {Boolean} [sanitizeHtml=false]
- *
- * `true` to HTML-encode the start and end brackets of existing HTML tags found
- * in the input string. This will escape `<` and `>` characters to `<` and
- * `>`, respectively.
- *
- * Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
- * but will remove the significance of existing HTML tags in the input string. If
- * you would like to maintain the significance of existing HTML tags while also
- * making the output HTML string safe, leave this option as `false` and use a
- * tool like https://github.com/cure53/DOMPurify (or others) on the input string
- * before running Autolinker.
- */
- this.sanitizeHtml = false; // default value just to get the above doc comment in the ES5 output and documentation generator
- /**
- * @private
- * @property {Autolinker.matcher.Matcher[]} matchers
- *
- * The {@link Autolinker.matcher.Matcher} instances for this Autolinker
- * instance.
- *
- * This is lazily created in {@link #getMatchers}.
- */
- this.matchers = null;
- /**
- * @private
- * @property {Autolinker.AnchorTagBuilder} tagBuilder
- *
- * The AnchorTagBuilder instance used to build match replacement anchor tags.
- * Note: this is lazily instantiated in the {@link #getTagBuilder} method.
- */
- this.tagBuilder = null;
- // Note: when `this.something` is used in the rhs of these assignments,
- // it refers to the default values set above the constructor
- this.urls = this.normalizeUrlsCfg(cfg.urls);
- this.email = typeof cfg.email === 'boolean' ? cfg.email : this.email;
- this.phone = typeof cfg.phone === 'boolean' ? cfg.phone : this.phone;
- this.hashtag = cfg.hashtag || this.hashtag;
- this.mention = cfg.mention || this.mention;
- this.newWindow = typeof cfg.newWindow === 'boolean' ? cfg.newWindow : this.newWindow;
- this.stripPrefix = this.normalizeStripPrefixCfg(cfg.stripPrefix);
- this.stripTrailingSlash = typeof cfg.stripTrailingSlash === 'boolean' ? cfg.stripTrailingSlash : this.stripTrailingSlash;
- this.decodePercentEncoding = typeof cfg.decodePercentEncoding === 'boolean' ? cfg.decodePercentEncoding : this.decodePercentEncoding;
- this.sanitizeHtml = cfg.sanitizeHtml || false;
- // Validate the value of the `mention` cfg
- var mention = this.mention;
- if (mention !== false && mention !== 'mastodon' && mention !== 'twitter' && mention !== 'instagram' && mention !== 'soundcloud') {
- throw new Error("invalid `mention` cfg - see docs");
- }
- // Validate the value of the `hashtag` cfg
- var hashtag = this.hashtag;
- if (hashtag !== false && hashtag !== 'twitter' && hashtag !== 'facebook' && hashtag !== 'instagram') {
- throw new Error("invalid `hashtag` cfg - see docs");
- }
- this.truncate = this.normalizeTruncateCfg(cfg.truncate);
- this.className = cfg.className || this.className;
- this.replaceFn = cfg.replaceFn || this.replaceFn;
- this.context = cfg.context || this;
- }
- /**
- * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
- * Hashtags, and Mentions found in the given chunk of HTML. Does not link URLs
- * found within HTML tags.
- *
- * For instance, if given the text: `You should go to http://www.yahoo.com`,
- * then the result will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
- *
- * Example:
- *
- * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
- * // Produces: "Go to google.com"
- *
- * @static
- * @param {String} textOrHtml The HTML or text to find matches within (depending
- * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #mention},
- * {@link #hashtag}, and {@link #mention} options are enabled).
- * @param {Object} [options] Any of the configuration options for the Autolinker
- * class, specified in an Object (map). See the class description for an
- * example call.
- * @return {String} The HTML text, with matches automatically linked.
- */
- Autolinker.link = function (textOrHtml, options) {
- var autolinker = new Autolinker(options);
- return autolinker.link(textOrHtml);
- };
- /**
- * Parses the input `textOrHtml` looking for URLs, email addresses, phone
- * numbers, username handles, and hashtags (depending on the configuration
- * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
- * objects describing those matches (without making any replacements).
- *
- * Note that if parsing multiple pieces of text, it is slightly more efficient
- * to create an Autolinker instance, and use the instance-level {@link #parse}
- * method.
- *
- * Example:
- *
- * var matches = Autolinker.parse( "Hello google.com, I am asdf@asdf.com", {
- * urls: true,
- * email: true
- * } );
- *
- * console.log( matches.length ); // 2
- * console.log( matches[ 0 ].getType() ); // 'url'
- * console.log( matches[ 0 ].getUrl() ); // 'google.com'
- * console.log( matches[ 1 ].getType() ); // 'email'
- * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
- *
- * @static
- * @param {String} textOrHtml The HTML or text to find matches within
- * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
- * {@link #hashtag}, and {@link #mention} options are enabled).
- * @param {Object} [options] Any of the configuration options for the Autolinker
- * class, specified in an Object (map). See the class description for an
- * example call.
- * @return {Autolinker.match.Match[]} The array of Matches found in the
- * given input `textOrHtml`.
- */
- Autolinker.parse = function (textOrHtml, options) {
- var autolinker = new Autolinker(options);
- return autolinker.parse(textOrHtml);
- };
- /**
- * Normalizes the {@link #urls} config into an Object with 3 properties:
- * `schemeMatches`, `wwwMatches`, and `tldMatches`, all Booleans.
- *
- * See {@link #urls} config for details.
- *
- * @private
- * @param {Boolean/Object} urls
- * @return {Object}
- */
- Autolinker.prototype.normalizeUrlsCfg = function (urls) {
- if (urls == null)
- urls = true; // default to `true`
- if (typeof urls === 'boolean') {
- return { schemeMatches: urls, wwwMatches: urls, tldMatches: urls };
- }
- else { // object form
- return {
- schemeMatches: typeof urls.schemeMatches === 'boolean' ? urls.schemeMatches : true,
- wwwMatches: typeof urls.wwwMatches === 'boolean' ? urls.wwwMatches : true,
- tldMatches: typeof urls.tldMatches === 'boolean' ? urls.tldMatches : true
- };
- }
- };
- /**
- * Normalizes the {@link #stripPrefix} config into an Object with 2
- * properties: `scheme`, and `www` - both Booleans.
- *
- * See {@link #stripPrefix} config for details.
- *
- * @private
- * @param {Boolean/Object} stripPrefix
- * @return {Object}
- */
- Autolinker.prototype.normalizeStripPrefixCfg = function (stripPrefix) {
- if (stripPrefix == null)
- stripPrefix = true; // default to `true`
- if (typeof stripPrefix === 'boolean') {
- return { scheme: stripPrefix, www: stripPrefix };
- }
- else { // object form
- return {
- scheme: typeof stripPrefix.scheme === 'boolean' ? stripPrefix.scheme : true,
- www: typeof stripPrefix.www === 'boolean' ? stripPrefix.www : true
- };
- }
- };
- /**
- * Normalizes the {@link #truncate} config into an Object with 2 properties:
- * `length` (Number), and `location` (String).
- *
- * See {@link #truncate} config for details.
- *
- * @private
- * @param {Number/Object} truncate
- * @return {Object}
- */
- Autolinker.prototype.normalizeTruncateCfg = function (truncate) {
- if (typeof truncate === 'number') {
- return { length: truncate, location: 'end' };
- }
- else { // object, or undefined/null
- return defaults(truncate || {}, {
- length: Number.POSITIVE_INFINITY,
- location: 'end'
- });
- }
- };
- /**
- * Parses the input `textOrHtml` looking for URLs, email addresses, phone
- * numbers, username handles, and hashtags (depending on the configuration
- * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
- * objects describing those matches (without making any replacements).
- *
- * This method is used by the {@link #link} method, but can also be used to
- * simply do parsing of the input in order to discover what kinds of links
- * there are and how many.
- *
- * Example usage:
- *
- * var autolinker = new Autolinker( {
- * urls: true,
- * email: true
- * } );
- *
- * var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
- *
- * console.log( matches.length ); // 2
- * console.log( matches[ 0 ].getType() ); // 'url'
- * console.log( matches[ 0 ].getUrl() ); // 'google.com'
- * console.log( matches[ 1 ].getType() ); // 'email'
- * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
- *
- * @param {String} textOrHtml The HTML or text to find matches within
- * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
- * {@link #hashtag}, and {@link #mention} options are enabled).
- * @return {Autolinker.match.Match[]} The array of Matches found in the
- * given input `textOrHtml`.
- */
- Autolinker.prototype.parse = function (textOrHtml) {
- var _this = this;
- var skipTagNames = ['a', 'style', 'script'], skipTagsStackCount = 0, // used to only Autolink text outside of anchor/script/style tags. We don't want to autolink something that is already linked inside of an tag, for instance
- matches = [];
- // Find all matches within the `textOrHtml` (but not matches that are
- // already nested within ,