Skip to content

Commit

Permalink
Merge branch 'fix/emoji-suggestions' into 'chapo-dev'
Browse files Browse the repository at this point in the history
Fix/emoji suggestions

Closes LemmyNet#152, LemmyNet#166, LemmyNet#168, and LemmyNet#173

See merge request chapo-sandbox/lemmy!311
  • Loading branch information
greatbearshark committed Aug 23, 2020
2 parents 45bc69c + 7c1d337 commit a17d503
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 20 deletions.
24 changes: 14 additions & 10 deletions ui/src/components/markdown-textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,23 @@ export class MarkdownTextArea extends Component<
}

if (this.props.finished && !prevProps.finished) {
let prevState = { ...this.state };
prevState.content = '';
prevState.loading = false;
prevState.previewMode = false;
this.setState(prevState);
this.setState({
content: '',
loading: false,
previewMode: false,
});
if (this.props.replyType) {
this.props.onReplyCancel();
}
}

// if initial content is passed down and there is no current content, update it
if (this.props.initialContent && this.state.content === '') {
this.setState({ content: this.props.initialContent })
} else {
// if initial content is passed down and there is no current content, update it
if (
this.props.initialContent &&
this.state.content === '' &&
!this.props.finished
) {
this.setState({ content: this.props.initialContent });
}
}
}

Expand Down
29 changes: 25 additions & 4 deletions ui/src/components/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { Icon } from './icon';
import { linkEvent } from '../linkEvent';
import { changeTheme, ThemeSelector } from '../theme';
import Button from './elements/Button';
import { Box } from 'theme-ui';
// import { Button } from 'theme-ui';
// import { changeTheme } from './ThemeSystemProvider';

Expand Down Expand Up @@ -253,6 +254,18 @@ class BaseUser extends Component<any, UserState> {
/>
)}
<span>/u/{this.state.username}</span>
<Box>
{this.state.pronouns && (
<span className="badge mr-1 comment-badge pronouns-badge">
{this.state.pronouns}
</span>
)}
{this.state.additionalPronouns && (
<span className="badge mr-1 comment-badge pronouns-badge">
{this.state.additionalPronouns}
</span>
)}
</Box>
</h5>
{this.state.loading && (
<h5>
Expand Down Expand Up @@ -298,7 +311,9 @@ class BaseUser extends Component<any, UserState> {
<div className="btn-group btn-group-toggle">
<Button
as="label"
variant={this.state.view == UserDetailsView.Overview ? 'primary' : 'muted'}
variant={
this.state.view == UserDetailsView.Overview ? 'primary' : 'muted'
}
>
<input
type="radio"
Expand All @@ -311,7 +326,9 @@ class BaseUser extends Component<any, UserState> {
</Button>
<Button
as="label"
variant={this.state.view == UserDetailsView.Comments ? 'primary' : 'muted'}
variant={
this.state.view == UserDetailsView.Comments ? 'primary' : 'muted'
}
>
<input
type="radio"
Expand All @@ -324,7 +341,9 @@ class BaseUser extends Component<any, UserState> {
</Button>
<Button
as="label"
variant={this.state.view == UserDetailsView.Posts ? 'primary' : 'muted'}
variant={
this.state.view == UserDetailsView.Posts ? 'primary' : 'muted'
}
>
<input
type="radio"
Expand All @@ -337,7 +356,9 @@ class BaseUser extends Component<any, UserState> {
</Button>
<Button
as="label"
variant={this.state.view == UserDetailsView.Saved ? 'primary' : 'muted'}
variant={
this.state.view == UserDetailsView.Saved ? 'primary' : 'muted'
}
>
<input
className="visually-hidden"
Expand Down
228 changes: 228 additions & 0 deletions ui/src/normalize-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/**
* Copied from this repo due to ES Module compatibility issues
* https://github.com/sindresorhus/normalize-url
*
*/

// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
const DATA_URL_DEFAULT_MIME_TYPE = 'text/plain';
const DATA_URL_DEFAULT_CHARSET = 'us-ascii';

const testParameter = (name, filters) => {
return filters.some(filter =>
filter instanceof RegExp ? filter.test(name) : filter === name
);
};

const normalizeDataURL = (urlString, { stripHash }) => {
const match = /^data:(?<type>.*?),(?<data>.*?)(?:#(?<hash>.*))?$/.exec(
urlString
);

if (!match) {
throw new Error(`Invalid URL: ${urlString}`);
}

let { type, data, hash } = match.groups;
const mediaType = type.split(';');
hash = stripHash ? '' : hash;

let isBase64 = false;
if (mediaType[mediaType.length - 1] === 'base64') {
mediaType.pop();
isBase64 = true;
}

// Lowercase MIME type
const mimeType = (mediaType.shift() || '').toLowerCase();
const attributes = mediaType
.map(attribute => {
let [key, value = ''] = attribute.split('=').map(string => string.trim());

// Lowercase `charset`
if (key === 'charset') {
value = value.toLowerCase();

if (value === DATA_URL_DEFAULT_CHARSET) {
return '';
}
}

return `${key}${value ? `=${value}` : ''}`;
})
.filter(Boolean);

const normalizedMediaType = [...attributes];

if (isBase64) {
normalizedMediaType.push('base64');
}

if (
normalizedMediaType.length !== 0 ||
(mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE)
) {
normalizedMediaType.unshift(mimeType);
}

return `data:${normalizedMediaType.join(';')},${
isBase64 ? data.trim() : data
}${hash ? `#${hash}` : ''}`;
};

const normalizeUrl = (urlString: string, options?: any) => {
options = {
defaultProtocol: 'http:',
normalizeProtocol: true,
forceHttp: false,
forceHttps: false,
stripAuthentication: true,
stripHash: false,
stripWWW: true,
removeQueryParameters: [/^utm_\w+/i],
removeTrailingSlash: true,
removeDirectoryIndex: false,
sortQueryParameters: true,
...options,
};

urlString = urlString.trim();

// Data URL
if (/^data:/i.test(urlString)) {
return normalizeDataURL(urlString, options);
}

const hasRelativeProtocol = urlString.startsWith('//');
const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString);

// Prepend protocol
if (!isRelativeUrl) {
urlString = urlString.replace(
/^(?!(?:\w+:)?\/\/)|^\/\//,
options.defaultProtocol
);
}

const urlObj = new URL(urlString);

if (options.forceHttp && options.forceHttps) {
throw new Error(
'The `forceHttp` and `forceHttps` options cannot be used together'
);
}

if (options.forceHttp && urlObj.protocol === 'https:') {
urlObj.protocol = 'http:';
}

if (options.forceHttps && urlObj.protocol === 'http:') {
urlObj.protocol = 'https:';
}

// Remove auth
if (options.stripAuthentication) {
urlObj.username = '';
urlObj.password = '';
}

// Remove hash
if (options.stripHash) {
urlObj.hash = '';
}

// Remove duplicate slashes if not preceded by a protocol
if (urlObj.pathname) {
urlObj.pathname = urlObj.pathname.replace(
/(?<!\b[a-z][\d+.a-z\-]{1,50}:)\/{2,}/g,
'/'
);
}

// Decode URI octets
if (urlObj.pathname) {
try {
urlObj.pathname = decodeURI(urlObj.pathname);
} catch {}
}

// Remove directory index
if (options.removeDirectoryIndex === true) {
options.removeDirectoryIndex = [/^index\.[a-z]+$/];
}

if (
Array.isArray(options.removeDirectoryIndex) &&
options.removeDirectoryIndex.length > 0
) {
let pathComponents = urlObj.pathname.split('/');
const lastComponent = pathComponents[pathComponents.length - 1];

if (testParameter(lastComponent, options.removeDirectoryIndex)) {
pathComponents = pathComponents.slice(0, pathComponents.length - 1);
urlObj.pathname = pathComponents.slice(1).join('/') + '/';
}
}

if (urlObj.hostname) {
// Remove trailing dot
urlObj.hostname = urlObj.hostname.replace(/\.$/, '');

// Remove `www.`
if (
options.stripWWW &&
/^www\.(?!www\.)[\da-z\-]{1,63}\.[\d.a-z\-]{2,63}$/.test(
urlObj.hostname
)
) {
// Each label should be max 63 at length (min: 1).
// Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
// Each TLD should be up to 63 characters long (min: 2).
// It is technically possible to have a single character TLD, but none currently exist.
urlObj.hostname = urlObj.hostname.replace(/^www\./, '');
}
}

// Remove query unwanted parameters
if (Array.isArray(options.removeQueryParameters)) {
for (const key of [...urlObj.searchParams.keys()]) {
if (testParameter(key, options.removeQueryParameters)) {
urlObj.searchParams.delete(key);
}
}
}

// Sort query parameters
if (options.sortQueryParameters) {
urlObj.searchParams.sort();
}

if (options.removeTrailingSlash) {
urlObj.pathname = urlObj.pathname.replace(/\/$/, '');
}

// Take advantage of many of the Node `url` normalizations
urlString = urlObj.toString();

// Remove ending `/`
if (
(options.removeTrailingSlash || urlObj.pathname === '/') &&
urlObj.hash === ''
) {
urlString = urlString.replace(/\/$/, '');
}

// Restore relative protocol, if applicable
if (hasRelativeProtocol && !options.normalizeProtocol) {
urlString = urlString.replace(/^http:\/\//, '//');
}

// Remove http/https
if (options.stripProtocol) {
urlString = urlString.replace(/^(?:https?:)?\/\//, '');
}

return urlString;
};

export default normalizeUrl;
26 changes: 22 additions & 4 deletions ui/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
CommunityModsState,
} from './interfaces';
import { UserService, WebSocketService } from './services';
import emojiPaths from './emoji-paths.json';

import Tribute from 'tributejs/src/Tribute.js';
import markdown_it from 'markdown-it';
Expand All @@ -60,10 +61,15 @@ import Toastify from 'toastify-js';
import tippy from 'tippy.js';
// import { EmojiButton } from '@joeattardi/emoji-button';

import { customEmojis, replaceEmojis } from './custom-emojis';
import {
customEmojis,
emojiReplacements,
replaceEmojis,
} from './custom-emojis';
import moment from 'moment';
import { BASE_PATH, isProduction } from './isProduction';
import axios from 'axios';
import normalizeUrl from './normalize-url';

export const repoUrl = 'https://gitlab.com/chapo-sandbox/production';
export const helpGuideUrl = '/docs/about_guide.html';
Expand Down Expand Up @@ -187,6 +193,17 @@ export const md = new markdown_it({
defs: objectFlip(emojiShortName),
})
.use(iterator, 'url_new_win', 'link_open', function (tokens, idx) {
// in markdown, if a link tag is missing a protocol, add it
if (tokens[idx].type === 'link_open') {
const hrefIndex = tokens[idx].attrs.findIndex(
attribute => attribute[0] === 'href'
);
if (hrefIndex !== -1) {
const hrefValue = tokens[idx].attrs[hrefIndex][1];
tokens[idx].attrs[hrefIndex][1] = normalizeUrl(hrefValue);
}
}

// make sure all inline links open in a new window and don't include the referrer
tokens[idx].attrPush(['target', '_blank']);
tokens[idx].attrPush(['rel', 'noreferrer']);
Expand Down Expand Up @@ -619,14 +636,15 @@ export function setupTribute(): Tribute {
return `:${item.original.key}:`;
},
values: [
// ...Object.entries(emojiShortName).map(e => {
// return { key: e[1], val: e[0] };
// ...(emojiPaths).map(val => {
// return { key: val.split('.')[0], val: val };
// }),
{
key: 'logo',
val: `<img className="icon icon-navbar" src="${BASE_PATH}logo.png" alt="vaporwave hammer and sickle logo, courtesy of ancestral potato">`,
},
...customEmojis,
// ...customEmojis,
...emojiReplacements,
],
allowSpaces: false,
autocompleteMode: true,
Expand Down

0 comments on commit a17d503

Please sign in to comment.