Files
mattermost-mobile/app/utils/markdown/index.ts
Elias Nahum 7aa5bd0611 Update Dependencies and bug fixes (#7000)
* update dependencies

* update dependencies

* feedback review

* update @mattermost/react-native-turbo-mailer
2023-01-24 09:14:23 +02:00

266 lines
11 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Platform, StyleProp, StyleSheet, TextStyle} from 'react-native';
import {getViewPortWidth} from '@utils/images';
import {changeOpacity, concatStyles, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import type {MarkdownTextStyles} from '@typings/global/markdown';
type LanguageObject = {
[key: string]: {
name: string;
extensions: string[];
aliases?: Set<string>;
};
}
export function getCodeFont() {
return Platform.OS === 'ios' ? 'Menlo' : 'monospace';
}
export const getMarkdownTextStyles = makeStyleSheetFromTheme((theme: Theme) => {
const codeFont = getCodeFont();
return {
emph: {
fontFamily: 'OpenSans-Italic',
},
strong: {
fontFamily: 'OpenSans-SemiBold',
fontWeight: '600',
},
del: {
textDecorationLine: 'line-through',
},
link: {
color: theme.linkColor,
fontFamily: 'OpenSans',
},
heading1: {
...typography('Heading', 700),
},
heading1Text: {
paddingTop: 12,
paddingBottom: 6,
},
heading2: {
...typography('Heading', 600),
},
heading2Text: {
paddingTop: 12,
paddingBottom: 6,
},
heading3: {
...typography('Heading', 500),
},
heading3Text: {
paddingTop: 12,
paddingBottom: 6,
},
heading4: {
...typography('Heading', 400),
},
heading4Text: {
paddingTop: 12,
paddingBottom: 6,
},
heading5: {
...typography('Heading', 300),
},
heading5Text: {
paddingTop: 12,
paddingBottom: 6,
},
heading6: {
...typography('Heading', 200),
},
heading6Text: {
paddingTop: 12,
paddingBottom: 6,
},
code: {
alignSelf: 'center',
backgroundColor: changeOpacity(theme.centerChannelColor, 0.07),
fontFamily: codeFont,
},
codeBlock: {
fontFamily: codeFont,
},
mention: {
fontFamily: 'OpenSans',
color: theme.linkColor,
},
error: {
fontFamily: 'OpenSans',
color: theme.errorTextColor,
},
table_header_row: {
fontFamily: 'OpenSans-Bold',
},
mention_highlight: {
color: theme.mentionHighlightLink,
},
search_highlight: {
backgroundColor: theme.mentionHighlightBg,
color: theme.mentionHighlightLink,
},
};
});
export const getMarkdownBlockStyles = makeStyleSheetFromTheme((theme: Theme) => {
return {
adjacentParagraph: {
marginTop: 8,
},
horizontalRule: {
backgroundColor: theme.centerChannelColor,
height: StyleSheet.hairlineWidth,
marginVertical: 10,
},
quoteBlockIcon: {
color: changeOpacity(theme.centerChannelColor, 0.5),
},
};
});
const highlightedLanguages: LanguageObject = {
actionscript: {name: 'ActionScript', extensions: ['as'], aliases: new Set(['as', 'as3'])},
applescript: {name: 'AppleScript', extensions: ['applescript', 'osascript', 'scpt']},
bash: {name: 'Bash', extensions: ['sh'], aliases: new Set('sh')},
clojure: {name: 'Clojure', extensions: ['clj', 'boot', 'cl2', 'cljc', 'cljs', 'cljs.hl', 'cljscm', 'cljx', 'hic']},
coffeescript: {name: 'CoffeeScript', extensions: ['coffee', '_coffee', 'cake', 'cjsx', 'cson', 'iced'], aliases: new Set(['coffee', 'coffee-script'])},
cpp: {name: 'C/C++', extensions: ['cpp', 'c', 'cc', 'h', 'c++', 'h++', 'hpp'], aliases: new Set(['c++', 'c'])},
cs: {name: 'C#', extensions: ['cs', 'csharp'], aliases: new Set(['c#', 'csharp'])},
css: {name: 'CSS', extensions: ['css']},
d: {name: 'D', extensions: ['d', 'di'], aliases: new Set('dlang')},
dart: {name: 'Dart', extensions: ['dart']},
delphi: {name: 'Delphi', extensions: ['delphi', 'dpr', 'dfm', 'pas', 'pascal', 'freepascal', 'lazarus', 'lpr', 'lfm']},
diff: {name: 'Diff', extensions: ['diff', 'patch'], aliases: new Set(['patch', 'udiff'])},
django: {name: 'Django', extensions: ['django', 'jinja']},
dockerfile: {name: 'Dockerfile', extensions: ['dockerfile', 'docker'], aliases: new Set('docker')},
elixir: {name: 'Elixir', extensions: ['ex', 'exs'], aliases: new Set(['ex', 'exs'])},
erlang: {name: 'Erlang', extensions: ['erl'], aliases: new Set('erl')},
fortran: {name: 'Fortran', extensions: ['f90', 'f95']},
fsharp: {name: 'F#', extensions: ['fsharp', 'fs']},
gcode: {name: 'G-Code', extensions: ['gcode', 'nc']},
go: {name: 'Go', extensions: ['go'], aliases: new Set('golang')},
groovy: {name: 'Groovy', extensions: ['groovy']},
handlebars: {name: 'Handlebars', extensions: ['handlebars', 'hbs', 'html.hbs', 'html.handlebars'], aliases: new Set(['hbs', 'mustache'])},
haskell: {name: 'Haskell', extensions: ['hs'], aliases: new Set('hs')},
haxe: {name: 'Haxe', extensions: ['hx']},
java: {name: 'Java', extensions: ['java', 'jsp']},
javascript: {name: 'JavaScript', extensions: ['js', 'jsx'], aliases: new Set(['js', 'jsx'])},
json: {name: 'JSON', extensions: ['json']},
julia: {name: 'Julia', extensions: ['jl'], aliases: new Set('jl')},
kotlin: {name: 'Kotlin', extensions: ['kt', 'ktm', 'kts']},
latex: {name: 'LaTeX', extensions: ['tex'], aliases: new Set('tex')},
less: {name: 'Less', extensions: ['less']},
lisp: {name: 'Lisp', extensions: ['lisp']},
lua: {name: 'Lua', extensions: ['lua']},
makefile: {name: 'Makefile', extensions: ['mk', 'mak'], aliases: new Set(['make', 'mf', 'gnumake', 'bsdmake'])},
markdown: {name: 'Markdown', extensions: ['md', 'mkdown', 'mkd'], aliases: new Set(['md', 'mkd'])},
matlab: {name: 'Matlab', extensions: ['matlab', 'm'], aliases: new Set('m')},
objectivec: {name: 'Objective C', extensions: ['mm', 'objc', 'obj-c'], aliases: new Set(['objective_c', 'objc'])},
ocaml: {name: 'OCaml', extensions: ['ml']},
perl: {name: 'Perl', extensions: ['perl', 'pl'], aliases: new Set('pl')},
pgsql: {name: 'PostgreSQL', extensions: ['pgsql', 'postgres', 'postgresql'], aliases: new Set(['postgres', 'postgresql'])},
php: {name: 'PHP', extensions: ['php', 'php3', 'php4', 'php5', 'php6'], aliases: new Set(['php3', 'php4', 'php5'])},
powershell: {name: 'PowerShell', extensions: ['ps', 'ps1'], aliases: new Set('posh')},
puppet: {name: 'Puppet', extensions: ['pp'], aliases: new Set('pp')},
python: {name: 'Python', extensions: ['py', 'gyp'], aliases: new Set('py')},
r: {name: 'R', extensions: ['r'], aliases: new Set(['r', 's'])},
ruby: {name: 'Ruby', extensions: ['ruby', 'rb', 'gemspec', 'podspec', 'thor', 'irb'], aliases: new Set('rb')},
rust: {name: 'Rust', extensions: ['rs'], aliases: new Set('rs')},
scala: {name: 'Scala', extensions: ['scala']},
scheme: {name: 'Scheme', extensions: ['scm', 'sld']},
scss: {name: 'SCSS', extensions: ['scss']},
smalltalk: {name: 'Smalltalk', extensions: ['st'], aliases: new Set(['st', 'squeak'])},
sql: {name: 'SQL', extensions: ['sql']},
stylus: {name: 'Stylus', extensions: ['styl'], aliases: new Set('styl')},
swift: {name: 'Swift', extensions: ['swift']},
text: {name: 'Text', extensions: ['txt', 'log']},
typescript: {name: 'TypeScript', extensions: ['ts', 'tsx'], aliases: new Set(['ts', 'tsx'])},
vbnet: {name: 'VB.Net', extensions: ['vbnet', 'vb', 'bas'], aliases: new Set(['vb', 'visualbasic'])},
vbscript: {name: 'VBScript', extensions: ['vbs']},
verilog: {name: 'Verilog', extensions: ['v', 'veo', 'sv', 'svh']},
vhdl: {name: 'VHDL', extensions: ['vhd', 'vhdl']},
xml: {name: 'HTML, XML', extensions: ['xml', 'html', 'xhtml', 'rss', 'atom', 'xsl', 'plist']},
yaml: {name: 'YAML', extensions: ['yaml'], aliases: new Set('yml')},
};
export function getHighlightLanguageFromNameOrAlias(name: string) {
const langName: string = name.toLowerCase();
if (highlightedLanguages[langName]) {
return langName;
}
return Object.keys(highlightedLanguages).find((key) => {
return highlightedLanguages[key].aliases?.has(langName);
}) || '';
}
export function getHighlightLanguageName(language: string): string {
const name: string | undefined = getHighlightLanguageFromNameOrAlias(language);
if (!name) {
return '';
}
return highlightedLanguages[name].name || '';
}
export function escapeRegex(text: string) {
return text.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
}
export const getMarkdownImageSize = (
isReplyPost: boolean,
isTablet: boolean,
sourceSize?: SourceSize,
knownSize?: PostImage,
layoutWidth?: number,
layoutHeight?: number,
) => {
let ratioW;
let ratioH;
if (sourceSize?.width && sourceSize?.height) {
// if the source image is set with HxW
return {width: sourceSize.width, height: sourceSize.height};
} else if (knownSize?.width && knownSize.height) {
// If the metadata size is set calculate the ratio
ratioW = knownSize.width > 0 ? knownSize.height / knownSize.width : 1;
ratioH = knownSize.height > 0 ? knownSize.width / knownSize.height : 1;
}
if (sourceSize?.width && !sourceSize.height && ratioW) {
// If source Width is set calculate the height using the ratio
return {width: sourceSize.width, height: sourceSize.width * ratioW};
} else if (sourceSize?.height && !sourceSize.width && ratioH) {
// If source Height is set calculate the width using the ratio
return {width: sourceSize.height * ratioH, height: sourceSize.height};
}
if (sourceSize?.width || sourceSize?.height) {
// if at least one size is set and we do not have metadata (svg's)
const width = sourceSize.width;
const height = sourceSize.height;
return {width: width || height, height: height || width};
}
if (knownSize?.width && knownSize.height) {
// When metadata values are set
return {width: knownSize.width, height: knownSize.height};
}
// When no metadata and source size is not specified (full size svg's)
const width = layoutWidth || getViewPortWidth(isReplyPost, isTablet);
return {width, height: layoutHeight || width};
};
export const computeTextStyle = (textStyles: MarkdownTextStyles, baseStyle: StyleProp<TextStyle>, context: string[]) => {
const contextStyles: TextStyle[] = context.map((type) => textStyles[type]).filter((f) => f !== undefined);
return contextStyles.length ? concatStyles(baseStyle, contextStyles) : baseStyle;
};