diff --git a/.gitignore b/.gitignore index 33ed093..9fcc49c 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,6 @@ sftp-config.json ### VisualStudioCode ### .vscode/* + +### idea ### +.idea \ No newline at end of file diff --git a/package.json b/package.json index 5216871..c95bae8 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.8.1", "description": "Autolinking component for React Native", "author": "Josh Swan ", - "main": "src/index.js", + "main": "src/index.tsx", "license": "MIT", "repository": { "type": "git", @@ -28,7 +28,7 @@ "test": "mocha --require @babel/register --require test/setup.js --recursive test/*.test.js" }, "dependencies": { - "autolinker": "^3.0.3", + "autolinker": "^3.11.0", "prop-types": "^15.7.2" }, "devDependencies": { diff --git a/src/CollapsibleText.tsx b/src/CollapsibleText.tsx new file mode 100644 index 0000000..73cc3c6 --- /dev/null +++ b/src/CollapsibleText.tsx @@ -0,0 +1,128 @@ +import React, { + Component, +} from 'react'; +import PropTypes from 'prop-types'; +import { Icon } from 'core/common'; +import Util from 'core/util'; + +import { + View, + Image, + StyleSheet, + Animated, + Text, + TouchableOpacity, + ViewStyle, + Dimensions, + EmitterSubscription, + Platform +} from 'react-native'; +export default class CollapsibleText extends Component { + static propTypes = { + style: Text.propTypes?.style, + expandTextStyle:Text.propTypes?.style, + expandBorderStyle: ViewStyle, + numberOfLines: PropTypes.number, + rawText: PropTypes.string + } + + changEmitter: EmitterSubscription; + + textKey; + + constructor(props){ + super(props); + if (Platform.OS === 'harmony') { + this.textKey = 1; + } + this.state = { + /** 文本是否展开 */ + expanded:true, + numberOfLines:null, + /** 展开收起文字是否处于显示状态 */ + showExpandText:false, + /** 是否处于测量阶段 */ + measureFlag:true + } + this.numberOfLines = props.numberOfLines; + /** 文本是否需要展开收起功能:(实际文字内容是否超出numberOfLines限制) */ + this.needExpand = true; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + if(nextProps.rawText!==this.props.rawText){ + this.setState({expanded:true, numberOfLines:null, showExpandText:false, measureFlag:true}); + }} + + componentDidMount() { + this.changEmitter = Dimensions.addEventListener('change', this._onOrientationChange); + } + + componentWillUnmount() { + this.changEmitter?.remove(); + } + + _onOrientationChange = Util.Debounce((e) => { + if (this.textKey > 0) { + this.textKey += 1; + } + this.setState({expanded:true, numberOfLines:null, showExpandText:false, measureFlag:true}); + }, 500); + + _onPressExpand(){ + if(!this.state.expanded){ + this.setState({numberOfLines:null,expanded:true}) + }else{ + this.setState({numberOfLines:this.numberOfLines,expanded:false}) + } + } + + onTextLayout = (event) => { + if (this.state.measureFlag) { + if (event?.nativeEvent?.lines?.length > this.numberOfLines) { + this.setState({ expanded:false, showExpandText: true, numberOfLines: this.numberOfLines, measureFlag: false}); + } else { + this.setState({ showExpandText: false, numberOfLines:this.numberOfLines}); + } + } + }; + + render() { + const { numberOfLines, onLayout, expandTextStyle, expandBorderStyle, ...rest } = this.props; + const btnTitle = this.state.expanded ? '收起' : '全部'; + const iconName = this.state.expanded ? 'e605' : 'e606'; + let expandText = this.state.showExpandText?( + + + + {btnTitle} + + + + + ) : null; + const textProps = this.textKey > 0 ? { key: this.textKey } : {}; + return ( + + + {this.props.children} + + {expandText} + + ); + } +} + +const styles = StyleSheet.create({ + expandText: { + color:'#1890FF', + marginTop:0, + } +}); diff --git a/src/index.js b/src/index.tsx similarity index 81% rename from src/index.js rename to src/index.tsx index 80f0740..dbf0051 100644 --- a/src/index.js +++ b/src/index.tsx @@ -18,12 +18,13 @@ import { } from 'react-native'; import * as Truncate from './truncate'; import matchers from './matchers'; +import CollapsibleText from './CollapsibleText'; const tagBuilder = new AnchorTagBuilder(); const styles = StyleSheet.create({ link: { - color: '#0E7AFE', + color: '#1890FF', }, }); @@ -49,6 +50,15 @@ export default class Autolink extends PureComponent { return fn(text, truncate, truncateChars); } + constructor(props){ + super(props); + // Creates a token with a random UID that should not be guessable or + // conflict with other parts of the string. + // 注意UID 不要和自定义的matcher冲突 + this.uid = Math.floor(Math.random() * 0x1000).toString(16); + this.tokenRegexp = new RegExp(`(@__ELEMENT-${this.uid}-\\d+__@)`, 'g'); + } + onPress(match, alertShown) { const { onPress, @@ -194,12 +204,15 @@ export default class Autolink extends PureComponent { stripTrailingSlash, style, text, + text:rawText, truncate, truncateChars, truncateLocation, twitter, url, webFallback, + matchers: customMatchers, + expandTextStyle, ...other } = this.props; @@ -208,38 +221,16 @@ export default class Autolink extends PureComponent { mention = 'twitter'; } - // Creates a token with a random UID that should not be guessable or - // conflict with other parts of the string. - const uid = Math.floor(Math.random() * 0x10000000000).toString(16); - const tokenRegexp = new RegExp(`(@__ELEMENT-${uid}-\\d+__@)`, 'g'); - const generateToken = (() => { let counter = 0; - return () => `@__ELEMENT-${uid}-${counter++}__@`; // eslint-disable-line no-plusplus + return () => `@__ELEMENT-${this.uid}-${counter++}__@`; // eslint-disable-line no-plusplus })(); const matches = {}; try { - text = Autolinker.link(text || '', { - email, - hashtag, - mention, - phone, - urls: url, - stripPrefix, - stripTrailingSlash, - replaceFn: (match) => { - const token = generateToken(); - - matches[token] = match; - - return token; - }, - }); - // Custom matchers - matchers.forEach(({ id, regex, Match }) => { + [...matchers, ...customMatchers].forEach(({ id, regex, Match }) => { // eslint-disable-next-line react/destructuring-assignment if (this.props[id]) { text = text.replace(regex, (...args) => { @@ -257,6 +248,23 @@ export default class Autolink extends PureComponent { }); } }); + + text = Autolinker.link(text || '', { + email, + hashtag, + mention, + phone, + urls: url, + stripPrefix, + stripTrailingSlash, + replaceFn: (match) => { + const token = generateToken(); + + matches[token] = match; + + return token; + }, + }); } catch (e) { console.warn(e); // eslint-disable-line no-console @@ -264,7 +272,7 @@ export default class Autolink extends PureComponent { } const nodes = text - .split(tokenRegexp) + .split(this.tokenRegexp) .filter(part => !!part) .map((part, index) => { const match = matches[part]; @@ -286,11 +294,32 @@ export default class Autolink extends PureComponent { } }); - return createElement(Text, { - ref: (component) => { this._root = component; }, // eslint-disable-line no-underscore-dangle - style, - ...other, - }, ...nodes); + return ( + this.props.collapsibleTruncate + ?( { this._root = ref; }} // eslint-disable-line no-underscore-dangle + style={style} + expandTextStyle={expandTextStyle} + numberOfLines= { this.props.collapsibleTruncate } + rawText={rawText} + {...other} + > + {nodes} + ) + : ( { this._root = ref; }} // eslint-disable-line no-underscore-dangle + style={style} + {...other} + > + {nodes} + ) + ); + // return createElement(Text, { + // ref: (component) => { this._root = component; }, // eslint-disable-line no-underscore-dangle + // style, + // ...other, + // numberOfLines:1, + // }, ...nodes); } } @@ -309,9 +338,12 @@ Autolink.defaultProps = { twitter: false, url: true, webFallback: Platform.OS !== 'ios', // iOS requires LSApplicationQueriesSchemes for Linking.canOpenURL + matchers: [], + collapsibleTruncate: 0, }; Autolink.propTypes = { + collapsibleTruncate: PropTypes.number, email: PropTypes.bool, hashtag: PropTypes.oneOf([ false, @@ -356,4 +388,10 @@ Autolink.propTypes = { }), ]), webFallback: PropTypes.bool, + matchers: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string, + regex: PropTypes.string, + match: PropTypes.object + })), + expandTextStyle: Text.propTypes.style, }; diff --git a/src/matchers.js b/src/matchers.js index 3b58eb5..1788c1a 100644 --- a/src/matchers.js +++ b/src/matchers.js @@ -36,5 +36,5 @@ export default [ return this.latlng; } }, - }, +} ]; diff --git a/yarn.lock b/yarn.lock index 46647e9..605a8fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -958,12 +958,12 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autolinker@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-3.0.3.tgz#0a5227c76581a49cd72fc02d8601502c35426948" - integrity sha512-/ieTlOF7IYbjeGShX5eY0ALHb/jZJRxcavpEn3PelSiRPDn7yfGN/6IMKmiYq57mwB1aT7g1GG9ck8s9UrSwpQ== +autolinker@^3.11.0: + version "3.16.2" + resolved "https://r.cnpmjs.org/autolinker/-/autolinker-3.16.2.tgz#6bb4f32432fc111b65659336863e653973bfbcc9" + integrity sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA== dependencies: - tslib "^1.9.3" + tslib "^2.3.0" axobject-query@^2.0.2: version "2.0.2" @@ -5409,11 +5409,16 @@ trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" -tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +tslib@^2.3.0: + version "2.6.2" + resolved "https://r.cnpmjs.org/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"