diff --git a/gulpfile.ts b/gulpfile.ts index a688cc1e..44a585f2 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -24,15 +24,15 @@ process.env.FORCE_COLOR = '1'; type Callback = () => void; // prettier-ignore -const reload = (cb: Callback) => { browserSync.reload(); cb(); } -const clean = async () => exec(`rm -rf ${__dirname}/dist/*`); +const reload = (cb: Callback): void => { browserSync.reload(); cb(); } +const clean = async (): Promise => exec(`rm -rf ${__dirname}/dist/*`); export { clean, reload }; /** * Version bump the required files according to the version in package.json * Append link to changelog for current version in readme.txt */ -export function bump() { +export function bump(): NodeJS.ReadWriteStream { const re = `== Changelog ==\n(?!\n= ${VERSION})`; const repl = '== Changelog ==\n\n' + @@ -58,7 +58,7 @@ export function bump() { } // Translations -export function pot() { +export function pot(): NodeJS.ReadWriteStream { return gulp .src('./src/**/*.php', { base: 'dist/*' }) .pipe(sort()) @@ -80,7 +80,7 @@ export function pot() { // PHP/Static Asset Tasks // ================================================== -export function staticFiles() { +export function staticFiles(): NodeJS.ReadWriteStream { const misc = gulp .src('src/**/*.{po,pot,mo,html,txt,json,php}', { base: './src', @@ -101,7 +101,7 @@ export function staticFiles() { // Style Tasks // ================================================== -export function styles() { +export function styles(): NodeJS.ReadWriteStream { let stream = gulp.src('src/css/**/[^_]*.scss', { base: './src' }); if (!IS_PRODUCTION) { @@ -133,7 +133,7 @@ export function styles() { // Javascript Tasks // ================================================== -export function bundle(cb: Callback) { +export function bundle(cb: Callback): void { const args = IS_PRODUCTION ? ['-p'] : []; const child = spawn(`${__dirname}/node_modules/.bin/webpack`, args, { env: process.env, @@ -164,7 +164,7 @@ export function bundle(cb: Callback) { if (!IS_PRODUCTION) return cb(); } -export function js() { +export function js(): NodeJS.ReadWriteStream { let stream = gulp.src('src/**/*.js', { base: './src' }); if (IS_PRODUCTION) { stream = stream.pipe(uglify()); diff --git a/lib/scripts/before-test.ts b/lib/scripts/before-test.ts index 257ca783..60f179fb 100644 --- a/lib/scripts/before-test.ts +++ b/lib/scripts/before-test.ts @@ -19,11 +19,11 @@ window.ABT = { class Storage { private items = new Map(); - getItem(key: string) { + getItem(key: string): string | null { return this.items.get(key) || null; } - setItem(key: string, value: string) { + setItem(key: string, value: string): void { this.items.set(key, value); } } diff --git a/lib/scripts/update-styles.ts b/lib/scripts/update-styles.ts index 4f2e3e19..4b798966 100644 --- a/lib/scripts/update-styles.ts +++ b/lib/scripts/update-styles.ts @@ -69,7 +69,7 @@ async function getData(): Promise { } } `; - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { const options = { hostname: 'api.github.com', path: '/graphql', @@ -156,7 +156,7 @@ function getNewStyles(before: StyleData, after: StyleObj[]): string[] { return Array.from(newlyAddedStyles); } -async function main() { +(async (): Promise => { let newData: StyleData; try { newData = await getData().then(parseStyleObj); @@ -171,8 +171,4 @@ async function main() { path.resolve(__dirname, '../../src/vendor/', 'citation-styles.json'), JSON.stringify(newData, null, 4), ); -} - -main().catch(e => { - throw e; -}); +})(); diff --git a/lib/scripts/update-translations.ts b/lib/scripts/update-translations.ts index af0ba6fe..1864e4dd 100644 --- a/lib/scripts/update-translations.ts +++ b/lib/scripts/update-translations.ts @@ -77,7 +77,7 @@ const LANGS: ReadonlyMap = new Map([ ], ]); -async function getTranslations() { +async function getTranslations(): Promise { await exec(`rm -f ${ROOT_DIR}/src/languages/*`); const languages = (await sendRequest('/languages/list', {})).result.languages.filter( l => l.percentage > 0 && l.code !== 'en-us', @@ -101,7 +101,7 @@ async function getTranslations() { } } -async function updateTerms() { +async function updateTerms(): Promise { const req = ` curl -X POST https://api.poeditor.com/v2/projects/upload \ -F api_token="${TOKEN}" \ @@ -118,7 +118,7 @@ async function updateTerms() { ); } -async function updateTranslationStatus() { +async function updateTranslationStatus(): Promise { interface LangWithContribs extends Language { contributors: string[]; } @@ -182,14 +182,14 @@ async function updateTranslationStatus() { await writeFile(`${ROOT_DIR}/README.md`, newReadme); } -(async () => { +(async (): Promise => { await updateTerms(); await getTranslations(); await updateTranslationStatus(); })(); async function sendRequest(endpoint: string, data: object): Promise> { - return new Promise>((resolve, reject) => { + return new Promise>((resolve, reject): void => { const qs = stringify({ api_token: TOKEN, id: PROJECT_ID, diff --git a/lib/types/globals.d.ts b/lib/types/globals.d.ts index a88aa89f..4fe3db2e 100644 --- a/lib/types/globals.d.ts +++ b/lib/types/globals.d.ts @@ -77,4 +77,5 @@ declare module 'react-select-fast-filter-options'; declare module 'react-virtualized-select'; declare module 'rollbar-sourcemap-webpack-plugin'; declare module 'rollbar/dist/rollbar.umd'; +declare module 'string-hash'; declare module 'uglify-es'; diff --git a/package-lock.json b/package-lock.json index 0438c05a..c22a27f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "dev": true, "requires": { "@types/cheerio": "0.22.3", - "@types/react": "16.0.16" + "@types/react": "16.0.18" } }, "@types/glob": { @@ -198,10 +198,10 @@ "dev": true }, "@types/react": { - "version": "16.0.16", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.0.16.tgz", + "version": "16.0.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.0.18.tgz", "integrity": - "sha512-38DE4jdBCYUCa+YBIkcVMg7YKMexD2vLQU3j4DqDYuxnaecNYYJKe+mhR7b9hrNxocRpA+B+11ZvxRd1GlPMXg==", + "sha512-cUe8Dj0K0I826OLeC2BUsWa3DaGgRR2KaslmEI8R3Ma46dc52hqKIqC4iPQ30Jncz39E7JAytmxweUsu53QHZw==", "dev": true }, "@types/react-dom": { @@ -212,7 +212,7 @@ "dev": true, "requires": { "@types/node": "8.0.46", - "@types/react": "16.0.16" + "@types/react": "16.0.18" } }, "@types/react-motion": { @@ -222,7 +222,7 @@ "sha512-y0gHZ5Gghz4+/uQ8NLI35zFiOjI709wGrfbY+jC1EvoQwKbB9S+mCFDOk1s9o8C6+4OC5NcGigro1nPZ+ZceAg==", "dev": true, "requires": { - "@types/react": "16.0.16" + "@types/react": "16.0.18" } }, "@types/react-test-renderer": { @@ -233,7 +233,7 @@ "sha512-tbuDajGYu8tEBAGVXZqh0Hmfm+pZA9P+4UfMIWTvzO4PA8yBbL2TCsZNSky+S74oAKy96Z/PBdDTj22iPMYqcQ==", "dev": true, "requires": { - "@types/react": "16.0.16" + "@types/react": "16.0.18" } }, "@types/source-map": { @@ -14770,9 +14770,9 @@ "dev": true }, "string-hash": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.1.tgz", - "integrity": "sha1-joW+0pHgdjuPaAnZwzaP6gSNs9w=" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=" }, "string-length": { "version": "2.0.0", @@ -14915,6 +14915,13 @@ "string-hash": "1.1.1", "stylis": "3.3.2", "stylis-rule-sheet": "0.0.6" + }, + "dependencies": { + "string-hash": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.1.tgz", + "integrity": "sha1-joW+0pHgdjuPaAnZwzaP6gSNs9w=" + } } }, "stylis": { diff --git a/package.json b/package.json index bad4f64b..bb322b84 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@types/jest": "^21.1.4", "@types/jquery": "^3.2.15", "@types/node": "^8.0.46", - "@types/react": "^16.0.16", + "@types/react": "^16.0.18", "@types/react-dom": "^16.0.2", "@types/react-motion": "^0.0.23", "@types/react-test-renderer": "^16.0.0", @@ -105,6 +105,7 @@ "react-motion": "^0.5.2", "react-select-fast-filter-options": "^0.2.3", "react-virtualized-select": "^3.1.0", + "string-hash": "^1.1.3", "styled-jsx": "^2.1.2", "tinycolor2": "^1.4.1", "typescript": "^2.5.3", diff --git a/src/academic-bloggers-toolkit.pot b/src/academic-bloggers-toolkit.pot index c70815be..0472b63a 100644 --- a/src/academic-bloggers-toolkit.pot +++ b/src/academic-bloggers-toolkit.pot @@ -23,7 +23,7 @@ msgstr "" msgid "Notice: Rich editing must be enabled to use the Academic Blogger's Toolkit plugin" msgstr "" -#: php/backend.php:121 +#: php/backend.php:109 msgid "Reference List" msgstr "" diff --git a/src/js/components/badge.tsx b/src/js/components/badge.tsx index f5dd7134..4a8dd2ee 100644 --- a/src/js/components/badge.tsx +++ b/src/js/components/badge.tsx @@ -12,7 +12,7 @@ export default class Badge extends React.PureComponent { static defaultProps: Partial = { color: `${colors.blue}`, }; - render() { + render(): JSX.Element { const { count, color } = this.props; return (
diff --git a/src/js/components/button.tsx b/src/js/components/button.tsx index 66a28b05..d49e6c8c 100644 --- a/src/js/components/button.tsx +++ b/src/js/components/button.tsx @@ -36,15 +36,15 @@ export default class Button extends React.PureComponent { }; } - hideTooltip = () => { + hideTooltip = (): void => { this.setState(prev => ({ ...prev, isShowingTooltip: false })); }; - openLink = () => { + openLink = (): void => { window.open(this.props.href, '_blank'); }; - showTooltip = (e: React.MouseEvent) => { + showTooltip = (e: React.MouseEvent): void => { const { position } = this.props.tooltip!; const rect = e.currentTarget.getBoundingClientRect(); this.setState(() => ({ @@ -55,7 +55,7 @@ export default class Button extends React.PureComponent { // Below is disabled because the class fallbacks aren't complexity-adding. // tslint:disable-next-line cyclomatic-complexity - render() { + render(): JSX.Element { const { flat, focusable, diff --git a/src/js/components/callout.tsx b/src/js/components/callout.tsx index 23467873..efdab8e4 100644 --- a/src/js/components/callout.tsx +++ b/src/js/components/callout.tsx @@ -24,7 +24,7 @@ export default class Callout extends React.PureComponent { error: top.ABT.i18n.errors.prefix, }; - render() { + render(): JSX.Element | null { const { title, children, intent, isVisible, onDismiss, ...divProps } = this.props; const defaultTitle = intent === 'danger' ? Callout.prefixes.error : Callout.prefixes.warning; diff --git a/src/js/components/spinner.tsx b/src/js/components/spinner.tsx index 4db1cc90..a7e605b4 100644 --- a/src/js/components/spinner.tsx +++ b/src/js/components/spinner.tsx @@ -35,7 +35,7 @@ export default class Spinner extends React.PureComponent { this.style.backgroundColor = this.props.overlay ? undefined : this.props.bgColor; } - render() { + render(): JSX.Element { const cn = this.props.overlay ? 'abt-spinner abt-spinner_overlay' : 'abt-spinner'; return (
diff --git a/src/js/components/toggle-switch.tsx b/src/js/components/toggle-switch.tsx index 3fe408fd..14c4245e 100644 --- a/src/js/components/toggle-switch.tsx +++ b/src/js/components/toggle-switch.tsx @@ -23,9 +23,9 @@ export default class ToggleSwitch extends React.Component { }; } - hideTooltip = () => this.setState(prev => ({ ...prev, isShowingTooltip: false })); + hideTooltip = (): void => this.setState(prev => ({ ...prev, isShowingTooltip: false })); - showTooltip = (e: React.MouseEvent) => { + showTooltip = (e: React.MouseEvent): void => { const { position } = this.props.tooltip; const rect = e.currentTarget.getBoundingClientRect(); this.setState(() => ({ @@ -34,7 +34,7 @@ export default class ToggleSwitch extends React.Component { })); }; - render() { + render(): JSX.Element { const { checked, disabled, onChange, tooltip } = this.props; const { isShowingTooltip, transform } = this.state; return ( diff --git a/src/js/components/tooltip.tsx b/src/js/components/tooltip.tsx index d554f688..f0d79ae0 100644 --- a/src/js/components/tooltip.tsx +++ b/src/js/components/tooltip.tsx @@ -54,7 +54,7 @@ export default class Tooltip extends React.PureComponent { translateX(-5px)`; } }; - render() { + render(): JSX.Element { const { active, id, text, transform } = this.props; return (
{ + private receiveMessage = (e: MessageEvent): void => { if (e.data[0] === 'done') { const localeObj: LocaleCache = { time: Date.now(), diff --git a/src/js/core/processor.ts b/src/js/core/processor.ts index e2da024b..7cf1918a 100644 --- a/src/js/core/processor.ts +++ b/src/js/core/processor.ts @@ -91,9 +91,15 @@ export class Processor { * @param csl CSL.Data[]. */ prepareInlineCitationData(csl: CSL.Data[]): Citeproc.Citation { + // prettier-ignore + const citationItems = Array.from( + new Set( + csl.map(item => item.id) + ) + ).map(id => ({ id })); return { citationID: generateID(), - citationItems: csl.map(({ id }) => ({ id })), + citationItems, // FIXME: remove this when citeproc fixes the bug properties: {}, }; @@ -128,7 +134,7 @@ export class Processor { // "primes the pump" since citeproc currently runs synchronously await this.locales.fetch(locale); return { - retrieveItem: (id: string) => toJS(this.store.citations.CSL.get(id)!), + retrieveItem: (id: string): CSL.Data => toJS(this.store.citations.CSL.get(id)!), retrieveLocale: this.locales.retrieve, }; } diff --git a/src/js/dialogs/add/__tests__/manual-entry-container-test.tsx b/src/js/dialogs/add/__tests__/manual-entry-container-test.tsx index a0b35cc4..1333f8b6 100644 --- a/src/js/dialogs/add/__tests__/manual-entry-container-test.tsx +++ b/src/js/dialogs/add/__tests__/manual-entry-container-test.tsx @@ -67,6 +67,10 @@ describe('', () => { select.simulate('change', { currentTarget: { value: 'book' } }); expect(mocks.onTypeChange).toHaveBeenCalledTimes(1); }); + it('should not break when ref callback receives null', () => { + const { instance } = setup(); + expect(() => instance.focusTypeSelect(null)).not.toThrow(); + }); describe('wheel event tests', () => { const { component } = setup({ fullMount: false }); const wheelDiv = component.find('.bounded-rect'); diff --git a/src/js/dialogs/add/autocite.tsx b/src/js/dialogs/add/autocite.tsx index e31508e9..3119f364 100644 --- a/src/js/dialogs/add/autocite.tsx +++ b/src/js/dialogs/add/autocite.tsx @@ -31,20 +31,20 @@ export default class AutoCite extends React.Component { query = observable(''); @action - handleAutociteFieldChange = (e: React.ChangeEvent) => { + handleAutociteFieldChange = (e: React.ChangeEvent): void => { this.query.set(e.currentTarget.value); }; @action - handleQuery = () => { + handleQuery = (): void => { if (this.query.get().length === 0 || !this.input.validity.valid) return; this.props.getter(this.props.kind, this.query.get()); this.query.set(''); }; - bindRefs = (c: HTMLInputElement) => (this.input = c); + bindRefs = (c: HTMLInputElement): HTMLInputElement => (this.input = c); - handleKeyDown = (e: React.KeyboardEvent) => { + handleKeyDown = (e: React.KeyboardEvent): void => { if (e.key === 'Enter' || e.key === 'Return') { e.stopPropagation(); e.preventDefault(); @@ -52,7 +52,7 @@ export default class AutoCite extends React.Component { } }; - render() { + render(): JSX.Element { const { placeholder, kind } = this.props; return (
diff --git a/src/js/dialogs/add/button-row.tsx b/src/js/dialogs/add/button-row.tsx index 275a9caa..f99c9f98 100644 --- a/src/js/dialogs/add/button-row.tsx +++ b/src/js/dialogs/add/button-row.tsx @@ -25,7 +25,7 @@ interface Props { @observer export default class ButtonRow extends React.Component { static readonly labels = top.ABT.i18n.dialogs.add.buttonRow; - render() { + render(): JSX.Element { const { addManually, attachInline, diff --git a/src/js/dialogs/add/identifier-input.tsx b/src/js/dialogs/add/identifier-input.tsx index a8f9ddcd..99c09c55 100644 --- a/src/js/dialogs/add/identifier-input.tsx +++ b/src/js/dialogs/add/identifier-input.tsx @@ -15,7 +15,7 @@ interface Props { export default class IdentifierInput extends React.Component { static readonly labels = top.ABT.i18n.dialogs.add.identifierInput; - render() { + render(): JSX.Element { return (