Render text blocks
This commit is contained in:
parent
ca4db66308
commit
307573830a
15 changed files with 662 additions and 345 deletions
72
client/js/components/Text.js
Normal file
72
client/js/components/Text.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
import React from 'react';
|
||||
import stringToRGB from 'utils/color';
|
||||
|
||||
function nickStyle(nick, color) {
|
||||
const style = {
|
||||
fontWeight: 400
|
||||
};
|
||||
|
||||
if (color) {
|
||||
style.color = stringToRGB(nick);
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
function renderBlock(block, coloredNick, key) {
|
||||
switch (block.type) {
|
||||
case 'text':
|
||||
return block.text;
|
||||
|
||||
case 'link':
|
||||
return (
|
||||
<a target="_blank" rel="noopener noreferrer" href={block.url} key={key}>
|
||||
{block.text}
|
||||
</a>
|
||||
);
|
||||
|
||||
case 'format':
|
||||
return (
|
||||
<span style={block.style} key={key}>
|
||||
{block.text}
|
||||
</span>
|
||||
);
|
||||
|
||||
case 'nick':
|
||||
return (
|
||||
<span
|
||||
className="message-sender"
|
||||
style={nickStyle(block.text, coloredNick)}
|
||||
key={key}
|
||||
>
|
||||
{block.text}
|
||||
</span>
|
||||
);
|
||||
|
||||
case 'events':
|
||||
return (
|
||||
<span className="message-events-more" key={key}>
|
||||
{block.text}
|
||||
</span>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const Text = ({ children, coloredNick }) => {
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
if (children.length > 1) {
|
||||
let key = 0;
|
||||
return children.map(block => renderBlock(block, coloredNick, key++));
|
||||
}
|
||||
if (children.length === 1) {
|
||||
return renderBlock(children[0], coloredNick);
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
export default Text;
|
|
@ -2,6 +2,7 @@ import React, { memo, useState, useEffect, useRef } from 'react';
|
|||
import Modal from 'react-modal';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { FiUsers, FiX } from 'react-icons/fi';
|
||||
import Text from 'components/Text';
|
||||
import useModal from 'components/modals/useModal';
|
||||
import Button from 'components/ui/Button';
|
||||
import { join } from 'state/channels';
|
||||
|
@ -33,7 +34,9 @@ const Channel = memo(({ network, name, topic, userCount, joined }) => {
|
|||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="modal-channel-topic">{linkify(topic)}</p>
|
||||
<p className="modal-channel-topic">
|
||||
<Text>{linkify(topic)}</Text>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import Modal from 'react-modal';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FiX } from 'react-icons/fi';
|
||||
import Text from 'components/Text';
|
||||
import Button from 'components/ui/Button';
|
||||
import useModal from 'components/modals/useModal';
|
||||
import { getSelectedChannel } from 'state/channels';
|
||||
|
@ -18,7 +19,9 @@ const Topic = () => {
|
|||
<h2>Topic in {channel}</h2>
|
||||
<Button icon={FiX} className="modal-close" onClick={closeModal} />
|
||||
</div>
|
||||
<p className="modal-content">{linkify(topic)}</p>
|
||||
<p className="modal-content">
|
||||
<Text>{linkify(topic)}</Text>
|
||||
</p>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { memo } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Text from 'components/Text';
|
||||
import stringToRGB from 'utils/color';
|
||||
|
||||
const Message = ({ message, coloredNick, onNickClick }) => {
|
||||
|
@ -38,7 +39,10 @@ const Message = ({ message, coloredNick, onNickClick }) => {
|
|||
{message.from}
|
||||
</span>
|
||||
)}
|
||||
<span> {message.content}</span>
|
||||
<span>
|
||||
{' '}
|
||||
<Text coloredNick={coloredNick}>{message.content}</Text>
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { memo } from 'react';
|
||||
import Text from 'components/Text';
|
||||
import { timestamp, linkify } from 'utils';
|
||||
|
||||
const SearchResult = ({ result }) => {
|
||||
|
@ -16,7 +17,10 @@ const SearchResult = ({ result }) => {
|
|||
{' '}
|
||||
<span className="message-sender">{result.from}</span>
|
||||
</span>
|
||||
<span> {linkify(result.content)}</span>
|
||||
<span>
|
||||
{' '}
|
||||
<Text>{linkify(result.content)}</Text>
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,7 +14,6 @@ function createContext({ dispatch, getState }, { network, channel }) {
|
|||
};
|
||||
}
|
||||
|
||||
// TODO: Pull this out as convenience action
|
||||
function process({ dispatch, network, channel }, result) {
|
||||
if (typeof result === 'string') {
|
||||
dispatch(inform(result, network, channel));
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('message reducer', () => {
|
|||
'#chan1': [
|
||||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
content: [{ type: 'text', text: 'msg' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -54,17 +54,17 @@ describe('message reducer', () => {
|
|||
'#chan1': [
|
||||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
content: [{ type: 'text', text: 'msg' }]
|
||||
},
|
||||
{
|
||||
from: 'bar',
|
||||
content: 'msg'
|
||||
content: [{ type: 'text', text: 'msg' }]
|
||||
}
|
||||
],
|
||||
'#chan2': [
|
||||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
content: [{ type: 'text', text: 'msg' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -197,9 +197,13 @@ describe('message reducer', () => {
|
|||
|
||||
expect(messages.srv).not.toHaveProperty('srv');
|
||||
expect(messages.srv['#chan1']).toHaveLength(1);
|
||||
expect(messages.srv['#chan1'][0].content).toBe('test');
|
||||
expect(messages.srv['#chan1'][0].content).toMatchObject([
|
||||
{ type: 'text', text: 'test' }
|
||||
]);
|
||||
expect(messages.srv['#chan3']).toHaveLength(1);
|
||||
expect(messages.srv['#chan3'][0].content).toBe('test');
|
||||
expect(messages.srv['#chan3'][0].content).toMatchObject([
|
||||
{ type: 'text', text: 'test' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('deletes all messages related to network when disconnecting', () => {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import React from 'react';
|
||||
import { createSelector } from 'reselect';
|
||||
import has from 'lodash/has';
|
||||
import {
|
||||
|
@ -10,7 +9,6 @@ import {
|
|||
formatDate,
|
||||
unix
|
||||
} from 'utils';
|
||||
import stringToRGB from 'utils/color';
|
||||
import colorify from 'utils/colorify';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import { getApp } from './app';
|
||||
|
@ -48,6 +46,12 @@ function init(state, network, tab) {
|
|||
}
|
||||
}
|
||||
|
||||
function initNetworks(state, networks = []) {
|
||||
networks.forEach(({ host }) => {
|
||||
state[host] = {};
|
||||
});
|
||||
}
|
||||
|
||||
const collapsedEvents = ['join', 'part', 'quit'];
|
||||
|
||||
function shouldCollapse(msg1, msg2) {
|
||||
|
@ -59,54 +63,36 @@ function shouldCollapse(msg1, msg2) {
|
|||
);
|
||||
}
|
||||
|
||||
const eventVerbs = {
|
||||
join: 'joined the channel',
|
||||
part: 'left the channel',
|
||||
quit: 'quit'
|
||||
const blocks = {
|
||||
nick: nick => ({ type: 'nick', text: nick }),
|
||||
text: text => ({ type: 'text', text }),
|
||||
events: count => ({ type: 'events', text: `${count} more` })
|
||||
};
|
||||
|
||||
function renderNick(nick, type = '') {
|
||||
const style = {
|
||||
color: stringToRGB(nick),
|
||||
fontWeight: 400
|
||||
};
|
||||
|
||||
return (
|
||||
<span className="message-sender" style={style} key={`${nick} ${type}`}>
|
||||
{nick}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function renderMore(count, type) {
|
||||
return (
|
||||
<span
|
||||
className="message-events-more"
|
||||
key={`more ${type}`}
|
||||
>{`${count} more`}</span>
|
||||
);
|
||||
}
|
||||
const eventVerbs = {
|
||||
join: 'joined',
|
||||
part: 'left',
|
||||
quit: 'quit'
|
||||
};
|
||||
|
||||
function renderEvent(event, type, nicks) {
|
||||
const ending = eventVerbs[type];
|
||||
|
||||
if (nicks.length === 1) {
|
||||
event.push(renderNick(nicks[0], type));
|
||||
event.push(` ${ending}`);
|
||||
}
|
||||
if (nicks.length === 2) {
|
||||
event.push(renderNick(nicks[0], type));
|
||||
event.push(' and ');
|
||||
event.push(renderNick(nicks[1], type));
|
||||
event.push(` ${ending}`);
|
||||
}
|
||||
if (nicks.length > 2) {
|
||||
event.push(renderNick(nicks[0], type));
|
||||
event.push(', ');
|
||||
event.push(renderNick(nicks[1], type));
|
||||
event.push(' and ');
|
||||
event.push(renderMore(nicks.length - 2, type));
|
||||
event.push(` ${ending}`);
|
||||
event.push(blocks.nick(nicks[0]));
|
||||
event.push(blocks.text(` ${ending}`));
|
||||
} else if (nicks.length === 2) {
|
||||
event.push(blocks.nick(nicks[0]));
|
||||
event.push(blocks.text(' and '));
|
||||
event.push(blocks.nick(nicks[1]));
|
||||
event.push(blocks.text(` ${ending}`));
|
||||
} else if (nicks.length > 2) {
|
||||
event.push(blocks.nick(nicks[0]));
|
||||
event.push(blocks.text(', '));
|
||||
event.push(blocks.nick(nicks[1]));
|
||||
event.push(blocks.text(' and '));
|
||||
event.push(blocks.events(nicks.length - 2));
|
||||
event.push(blocks.text(` ${ending}`));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,32 +101,31 @@ function renderEvents(events) {
|
|||
if (first.type === 'nick') {
|
||||
const [oldNick, newNick] = first.params;
|
||||
|
||||
return [renderNick(oldNick), ' changed nick to ', renderNick(newNick)];
|
||||
return [
|
||||
blocks.nick(oldNick),
|
||||
blocks.text(' changed nick to '),
|
||||
blocks.nick(newNick)
|
||||
];
|
||||
}
|
||||
|
||||
if (first.type === 'kick') {
|
||||
const [kicked, by] = first.params;
|
||||
|
||||
return [renderNick(by), ' kicked ', renderNick(kicked)];
|
||||
return [blocks.nick(by), blocks.text(' kicked '), blocks.nick(kicked)];
|
||||
}
|
||||
|
||||
if (first.type === 'topic') {
|
||||
const [nick, newTopic] = first.params;
|
||||
const topic = colorify(linkify(newTopic));
|
||||
const [nick, topic] = first.params;
|
||||
|
||||
if (!topic) {
|
||||
return [renderNick(nick), ' cleared the topic'];
|
||||
return [blocks.nick(nick), blocks.text(' cleared the topic')];
|
||||
}
|
||||
|
||||
const result = [renderNick(nick), ' changed the topic to: '];
|
||||
|
||||
if (Array.isArray(topic)) {
|
||||
result.push(...topic);
|
||||
} else {
|
||||
result.push(topic);
|
||||
}
|
||||
|
||||
return result;
|
||||
return [
|
||||
blocks.nick(nick),
|
||||
blocks.text(' changed the topic to: '),
|
||||
...colorify(linkify(topic))
|
||||
];
|
||||
}
|
||||
|
||||
const byType = {};
|
||||
|
@ -163,14 +148,14 @@ function renderEvents(events) {
|
|||
|
||||
if (byType.part) {
|
||||
if (result.length > 1) {
|
||||
result[result.length - 1] += ', ';
|
||||
result[result.length - 1].text += ', ';
|
||||
}
|
||||
renderEvent(result, 'part', byType.part);
|
||||
}
|
||||
|
||||
if (byType.quit) {
|
||||
if (result.length > 1) {
|
||||
result[result.length - 1] += ', ';
|
||||
result[result.length - 1].text += ', ';
|
||||
}
|
||||
renderEvent(result, 'quit', byType.quit);
|
||||
}
|
||||
|
@ -448,12 +433,12 @@ export default createReducer(
|
|||
);
|
||||
},
|
||||
|
||||
[actions.INIT](state, { networks }) {
|
||||
initNetworks(state, networks);
|
||||
},
|
||||
|
||||
[actions.socket.NETWORKS](state, { data }) {
|
||||
if (data) {
|
||||
data.forEach(({ host }) => {
|
||||
state[host] = {};
|
||||
});
|
||||
}
|
||||
initNetworks(state, data);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import React from 'react';
|
||||
import TestRenderer from 'react-test-renderer';
|
||||
import {
|
||||
trimPrefixChar,
|
||||
isChannel,
|
||||
|
@ -9,8 +7,6 @@ import {
|
|||
} from '..';
|
||||
import linkify from '../linkify';
|
||||
|
||||
const render = el => TestRenderer.create(el).toJSON();
|
||||
|
||||
describe('trimPrefixChar()', () => {
|
||||
it('trims prefix characters', () => {
|
||||
expect(trimPrefixChar('##chan', '#')).toBe('chan');
|
||||
|
@ -95,21 +91,31 @@ describe('isValidUsername()', () => {
|
|||
|
||||
describe('linkify()', () => {
|
||||
const proto = href => (href.indexOf('http') !== 0 ? `http://${href}` : href);
|
||||
const linkTo = href =>
|
||||
render(
|
||||
<a href={proto(href)} rel="noopener noreferrer" target="_blank">
|
||||
{href}
|
||||
</a>
|
||||
);
|
||||
const linkTo = href => ({
|
||||
type: 'link',
|
||||
url: proto(href),
|
||||
text: href
|
||||
});
|
||||
const buildText = arr => {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (typeof arr[i] === 'string') {
|
||||
arr[i] = {
|
||||
type: 'text',
|
||||
text: arr[i]
|
||||
};
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
it('returns the arg when no matches are found', () =>
|
||||
[null, undefined, 10, false, true, 'just some text', ''].forEach(input =>
|
||||
expect(linkify(input)).toBe(input)
|
||||
it('returns a text block when no matches are found', () =>
|
||||
['just some text', ''].forEach(input =>
|
||||
expect(linkify(input)).toStrictEqual([{ type: 'text', text: input }])
|
||||
));
|
||||
|
||||
it('linkifies text', () =>
|
||||
Object.entries({
|
||||
'google.com': linkTo('google.com'),
|
||||
'google.com': [linkTo('google.com')],
|
||||
'google.com stuff': [linkTo('google.com'), ' stuff'],
|
||||
'cake google.com stuff': ['cake ', linkTo('google.com'), ' stuff'],
|
||||
'cake google.com stuff https://google.com': [
|
||||
|
@ -129,6 +135,6 @@ describe('linkify()', () => {
|
|||
'google.com ': [linkTo('google.com'), ' '],
|
||||
'/google.com?': ['/', linkTo('google.com'), '?']
|
||||
}).forEach(([input, expected]) =>
|
||||
expect(render(linkify(input))).toEqual(expected)
|
||||
expect(linkify(input)).toEqual(buildText(expected))
|
||||
));
|
||||
});
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import React from 'react';
|
||||
|
||||
const formatChars = {
|
||||
export const formatChars = {
|
||||
bold: 0x02,
|
||||
italic: 0x1d,
|
||||
underline: 0x1f,
|
||||
|
@ -10,7 +8,7 @@ const formatChars = {
|
|||
reset: 0x0f
|
||||
};
|
||||
|
||||
const colors = {
|
||||
export const colors = {
|
||||
0: 'white',
|
||||
1: 'black',
|
||||
2: 'blue',
|
||||
|
@ -234,7 +232,7 @@ function tokenize(str) {
|
|||
function colorifyString(str, state = {}) {
|
||||
const tokens = tokenize(str);
|
||||
|
||||
if (typeof tokens === 'string') {
|
||||
if (tokens === str) {
|
||||
return [tokens, state];
|
||||
}
|
||||
|
||||
|
@ -309,10 +307,17 @@ function colorifyString(str, state = {}) {
|
|||
|
||||
case 'text':
|
||||
if (Object.keys(style).length > 0) {
|
||||
result.push(<span style={style}>{token.content}</span>);
|
||||
result.push({
|
||||
type: 'format',
|
||||
style,
|
||||
text: token.content
|
||||
});
|
||||
style = { ...style };
|
||||
} else {
|
||||
result.push(token.content);
|
||||
result.push({
|
||||
type: 'text',
|
||||
text: token.content
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -323,34 +328,25 @@ function colorifyString(str, state = {}) {
|
|||
return [result, { style, reverse }];
|
||||
}
|
||||
|
||||
export default function colorify(input) {
|
||||
if (typeof input === 'string') {
|
||||
const [colored] = colorifyString(input);
|
||||
return colored;
|
||||
}
|
||||
export default function colorify(blocks) {
|
||||
const result = [];
|
||||
let colored;
|
||||
let state;
|
||||
|
||||
if (Array.isArray(input)) {
|
||||
const result = [];
|
||||
let state;
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
const block = blocks[i];
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (typeof input[i] === 'string') {
|
||||
const [colored, nextState] = colorifyString(input[i], state);
|
||||
|
||||
if (typeof colored === 'string') {
|
||||
result.push(colored);
|
||||
} else {
|
||||
result.push(...colored);
|
||||
}
|
||||
|
||||
state = nextState;
|
||||
if (block.type === 'text') {
|
||||
[colored, state] = colorifyString(block.text, state);
|
||||
if (colored !== block.text) {
|
||||
result.push(...colored);
|
||||
} else {
|
||||
result.push(input[i]);
|
||||
result.push(block);
|
||||
}
|
||||
} else {
|
||||
result.push(block);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return input;
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,20 +1,44 @@
|
|||
import Autolinker from 'autolinker';
|
||||
import React from 'react';
|
||||
|
||||
const autolinker = new Autolinker({
|
||||
stripPrefix: false,
|
||||
stripTrailingSlash: false
|
||||
});
|
||||
|
||||
function pushText(arr, text) {
|
||||
const last = arr[arr.length - 1];
|
||||
if (last?.type === 'text') {
|
||||
last.text += text;
|
||||
} else {
|
||||
arr.push({
|
||||
type: 'text',
|
||||
text
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function pushLink(arr, url, text) {
|
||||
arr.push({
|
||||
type: 'link',
|
||||
url,
|
||||
text
|
||||
});
|
||||
}
|
||||
|
||||
export default function linkify(text) {
|
||||
if (!text) {
|
||||
if (typeof text !== 'string') {
|
||||
return text;
|
||||
}
|
||||
|
||||
let matches = autolinker.parseText(text);
|
||||
|
||||
if (matches.length === 0) {
|
||||
return text;
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
text
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
const result = [];
|
||||
|
@ -26,46 +50,27 @@ export default function linkify(text) {
|
|||
|
||||
if (match.getType() === 'url') {
|
||||
if (match.offset > pos) {
|
||||
if (typeof result[result.length - 1] === 'string') {
|
||||
result[result.length - 1] += text.slice(pos, match.offset);
|
||||
} else {
|
||||
result.push(text.slice(pos, match.offset));
|
||||
}
|
||||
pushText(result, text.slice(pos, match.offset));
|
||||
}
|
||||
|
||||
result.push(
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={match.getAnchorHref()}
|
||||
key={i}
|
||||
>
|
||||
{match.matchedText}
|
||||
</a>
|
||||
);
|
||||
} else if (typeof result[result.length - 1] === 'string') {
|
||||
result[result.length - 1] += text.slice(
|
||||
pos,
|
||||
match.offset + match.matchedText.length
|
||||
);
|
||||
pushLink(result, match.getAnchorHref(), match.matchedText);
|
||||
} else {
|
||||
result.push(text.slice(pos, match.offset + match.matchedText.length));
|
||||
pushText(
|
||||
result,
|
||||
text.slice(pos, match.offset + match.matchedText.length)
|
||||
);
|
||||
}
|
||||
|
||||
pos = match.offset + match.matchedText.length;
|
||||
}
|
||||
|
||||
if (pos < text.length) {
|
||||
if (typeof result[result.length - 1] === 'string') {
|
||||
result[result.length - 1] += text.slice(pos);
|
||||
if (result[result.length - 1]?.type === 'text') {
|
||||
result[result.length - 1].text += text.slice(pos);
|
||||
} else {
|
||||
result.push(text.slice(pos));
|
||||
pushText(result, text.slice(pos));
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length === 1) {
|
||||
return result[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -2,37 +2,24 @@ const lineHeight = 24;
|
|||
const userListWidth = 200;
|
||||
const smallScreen = 600;
|
||||
|
||||
function findBreakpointsString(text, breakpoints, index) {
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text.charAt(i);
|
||||
|
||||
if (char === ' ') {
|
||||
breakpoints.push({ end: i + index, next: i + 1 + index });
|
||||
} else if (i !== text.length - 1 && (char === '-' || char === '?')) {
|
||||
breakpoints.push({ end: i + 1 + index, next: i + 1 + index });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function findBreakpoints(text) {
|
||||
export function findBreakpoints(blocks) {
|
||||
const breakpoints = [];
|
||||
let length = 0;
|
||||
|
||||
if (typeof text === 'string') {
|
||||
findBreakpointsString(text, breakpoints, length);
|
||||
length = text.length;
|
||||
} else if (Array.isArray(text)) {
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const node = text[i];
|
||||
for (let j = 0; j < blocks.length; j++) {
|
||||
const {text} = blocks[j];
|
||||
|
||||
if (typeof node === 'string') {
|
||||
findBreakpointsString(node, breakpoints, length);
|
||||
length += node.length;
|
||||
} else {
|
||||
findBreakpointsString(node.props.children, breakpoints, length);
|
||||
length += node.props.children.length;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text.charAt(i);
|
||||
|
||||
if (char === ' ') {
|
||||
breakpoints.push({ end: length + i, next: length + i + 1 });
|
||||
} else if (i !== text.length - 1 && (char === '-' || char === '?')) {
|
||||
breakpoints.push({ end: length + i + 1, next: length + i + 1 });
|
||||
}
|
||||
}
|
||||
|
||||
length += text.length;
|
||||
}
|
||||
|
||||
return [breakpoints, length];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue