From 1e7c3c3079318a709420eebd7a338cbfa4e3207b Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Wed, 26 Feb 2020 07:38:35 -0700 Subject: [PATCH] Types: Add random types to various files (#1933) --- lib/analytics/index.ts | 8 --- lib/analytics/tracks.ts | 42 ++++------------ lib/analytics/types.ts | 13 +++++ lib/global.d.ts | 19 +++++--- lib/note-info/index.tsx | 8 +-- lib/tag-field/index.tsx | 70 ++++++++++++++++++--------- lib/tag-input/index.tsx | 60 ++++++++++++++--------- lib/tag-list/index.tsx | 55 +++++++++++++-------- lib/tag-list/input.tsx | 27 ++++++----- lib/utils/ensure-platform-support.tsx | 4 +- package-lock.json | 6 +++ package.json | 1 + tsconfig.json | 3 +- 13 files changed, 185 insertions(+), 131 deletions(-) create mode 100644 lib/analytics/types.ts diff --git a/lib/analytics/index.ts b/lib/analytics/index.ts index 2d00866bc..dbe188087 100644 --- a/lib/analytics/index.ts +++ b/lib/analytics/index.ts @@ -4,18 +4,10 @@ const debug = require('debug')('analytics'); * Internal dependencies */ import './tracks'; -import { TKQItem } from './tracks'; let user: string; import * as T from '../types'; -declare global { - interface Window { - analyticsEnabled: boolean; - _tkq: TKQItem[]; - } -} - declare const config: { version: string }; const analytics = { diff --git a/lib/analytics/tracks.ts b/lib/analytics/tracks.ts index 07efa1b90..4dbfa1970 100644 --- a/lib/analytics/tracks.ts +++ b/lib/analytics/tracks.ts @@ -1,28 +1,8 @@ import * as T from '../types'; - -declare global { - interface Window { - wpcom: { - _tkq: TKQItem[]; - tracks: TracksAPI; - }; - } -} +import { TKQItem, TracksAPI } from './types'; declare const TRACKS_COOKIE_DOMAIN: string | undefined; -export type TKQItem = - | Function - | [keyof TracksAPI, ...(string | T.JSONSerializable)[]]; - -export type TracksAPI = { - storeContext: (c: object) => void; - identifyUser: (user: string, login: string) => void; - recordEvent: (name: string, props: T.JSONSerializable) => void; - setProperties: (properties: object) => void; - clearIdentity: () => void; -}; - type Query = Partial<{ _dl: string; _dr: string; @@ -41,10 +21,11 @@ type Query = Partial<{ anonId: string; }>; -window.wpcom = window.wpcom || {}; window._tkq = window._tkq || []; +window.wpcom = window.wpcom || { tracks: buildTracks() }; +window.wpcom.tracks = window.wpcom.tracks || buildTracks(); -window.wpcom.tracks = (function() { +function buildTracks() { let userId: string | null | undefined; let userIdType: string; let userLogin: string | null | undefined; @@ -277,19 +258,16 @@ window.wpcom.tracks = (function() { const sx = window.pageXOffset !== undefined ? window.pageXOffset - : ( - document.documentElement || + : ((document.documentElement || document.body.parentNode || - document.body - ).scrollLeft; + document.body) as HTMLElement).scrollLeft; + const sy = window.pageYOffset !== undefined ? window.pageYOffset - : ( - document.documentElement || + : ((document.documentElement || document.body.parentNode || - document.body - ).scrollTop; + document.body) as HTMLElement).scrollTop; query._sx = sx !== undefined ? sx : 0; query._sy = sy !== undefined ? sy : 0; @@ -410,4 +388,4 @@ window.wpcom.tracks = (function() { initQueue(); return API; -})(); +} diff --git a/lib/analytics/types.ts b/lib/analytics/types.ts new file mode 100644 index 000000000..2a0381418 --- /dev/null +++ b/lib/analytics/types.ts @@ -0,0 +1,13 @@ +import * as T from '../types'; + +export type TKQItem = + | Function + | [keyof TracksAPI, ...(string | T.JSONSerializable)[]]; + +export type TracksAPI = { + storeContext: (c: object) => void; + identifyUser: (user: string, login: string) => void; + recordEvent: (name: string, props: T.JSONSerializable) => void; + setProperties: (properties: object) => void; + clearIdentity: () => void; +}; diff --git a/lib/global.d.ts b/lib/global.d.ts index 5bfd6564d..ba87bef28 100644 --- a/lib/global.d.ts +++ b/lib/global.d.ts @@ -1,9 +1,14 @@ -declare var __TEST__: boolean; +import { TKQItem, TracksAPI } from './analytics/types'; -interface Window { - _tkq: Array; - testEvents: (string | [string, ...any[]])[]; - wpcom: { - tracks: object; - }; +declare global { + const __TEST__: boolean; + + interface Window { + analyticsEnabled: boolean; + testEvents: (string | [string, ...any[]])[]; + _tkq: TKQItem[] & { a: unknown }; + wpcom: { + tracks: TracksAPI; + }; + } } diff --git a/lib/note-info/index.tsx b/lib/note-info/index.tsx index 210f1a759..f5f86dc25 100644 --- a/lib/note-info/index.tsx +++ b/lib/note-info/index.tsx @@ -26,6 +26,8 @@ type StateProps = { }; type DispatchProps = { + onMarkdownNote: (note: T.NoteEntity, isMarkdown: boolean) => any; + onPinNote: (note: T.NoteEntity, shouldPinNote: boolean) => any; onOutsideClick: () => any; }; @@ -180,12 +182,12 @@ export class NoteInfo extends Component { this.props.onMarkdownNote(this.props.note, event.currentTarget.checked); } -function formatTimestamp(unixTime) { +function formatTimestamp(unixTime: number) { return format(unixTime * 1000, 'MMM d, yyyy h:mm a'); } // https://github.com/RadLikeWhoa/Countable -function wordCount(content) { +function wordCount(content: string) { const matches = (content || '') .replace(/[\u200B]+/, '') .trim() @@ -197,7 +199,7 @@ function wordCount(content) { // https://mathiasbynens.be/notes/javascript-unicode const surrogatePairs = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; -function characterCount(content) { +function characterCount(content: string) { return ( // then get the length (content || '') diff --git a/lib/tag-field/index.tsx b/lib/tag-field/index.tsx index 1cac0f80a..b22feb97e 100644 --- a/lib/tag-field/index.tsx +++ b/lib/tag-field/index.tsx @@ -1,6 +1,11 @@ -import React, { Component, KeyboardEventHandler } from 'react'; +import React, { + Component, + KeyboardEvent, + KeyboardEventHandler, + MouseEvent, + RefObject, +} from 'react'; import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; import { Overlay } from 'react-overlays'; import isEmailTag from '../utils/is-email-tag'; import { updateNoteTags } from '../state/domain/notes'; @@ -18,23 +23,42 @@ import { union, } from 'lodash'; +import * as S from '../state'; +import * as T from '../types'; + +type OwnProps = { + allTags: T.TagName[]; + note: T.NoteEntity; + storeFocusTagField: (focusSetter: () => any) => any; + storeHasFocus: (focusGetter: () => boolean) => any; + tags: T.TagName[]; + unusedTags: T.TagName[]; + usedTags: T.TagName[]; +}; + +type OwnState = { + selectedTag: T.TagName; + showEmailTooltip?: boolean; + tagInput: string; +}; + +type DispatchProps = { + updateNoteTags: (args: { note: T.NoteEntity; tags: T.TagEntity[] }) => any; +}; + +type Props = OwnProps & DispatchProps; + const KEY_BACKSPACE = 8; const KEY_TAB = 9; const KEY_RIGHT = 39; -export class TagField extends Component { - static displayName = 'TagField'; +export class TagField extends Component { + focusInput?: () => any; + hiddenTag?: RefObject | null; + inputHasFocus?: () => boolean; + tagInput?: RefObject | null; - static propTypes = { - allTags: PropTypes.array.isRequired, - note: PropTypes.object.isRequired, - storeFocusTagField: PropTypes.func, - storeHasFocus: PropTypes.func, - tags: PropTypes.array.isRequired, - unusedTags: PropTypes.arrayOf(PropTypes.string), - updateNoteTags: PropTypes.func.isRequired, - usedTags: PropTypes.arrayOf(PropTypes.string), - }; + static displayName = 'TagField'; static defaultProps = { storeFocusTagField: noop, @@ -64,7 +88,7 @@ export class TagField extends Component { } } - addTag = tags => { + addTag = (tags: string) => { const { allTags, tags: existingTags } = this.props; const newTags = tags @@ -90,7 +114,7 @@ export class TagField extends Component { hasSelection = () => this.state.selectedTag && !!this.state.selectedTag.length; - deleteTag = tagName => { + deleteTag = (tagName: T.TagName) => { const { tags } = this.props; const { selectedTag } = this.state; @@ -117,7 +141,7 @@ export class TagField extends Component { focusTagField = () => this.focusInput && this.focusInput(); - interceptKeys: KeyboardEventHandler = e => { + interceptKeys: KeyboardEventHandler = e => { if (KEY_BACKSPACE === e.which) { if (this.hasSelection()) { this.deleteSelection(); @@ -148,7 +172,7 @@ export class TagField extends Component { selectLastTag = () => this.setState({ selectedTag: this.props.tags.slice(-1).shift() }); - selectTag = event => { + selectTag = (event: MouseEvent) => { const { target: { dataset: { tagName }, @@ -167,7 +191,7 @@ export class TagField extends Component { setTimeout(() => this.setState({ showEmailTooltip: false }), 5000); }; - onKeyDown = e => { + onKeyDown = (e: KeyboardEvent) => { if (this.state.showEmailTooltip) { this.hideEmailTooltip(); } @@ -183,10 +207,10 @@ export class TagField extends Component { storeInputRef = r => (this.tagInput = r); - storeTagInput = (value, callback) => + storeTagInput = (value: string, callback?: (...args: any) => any) => this.setState({ tagInput: value }, callback); - unselect = event => { + unselect = (event: KeyboardEvent) => { if (!this.state.selectedTag) { return; } @@ -249,4 +273,6 @@ export class TagField extends Component { } } -export default connect(null, { updateNoteTags })(TagField); +export default connect(null, { updateNoteTags } as S.MapDispatch< + DispatchProps +>)(TagField); diff --git a/lib/tag-input/index.tsx b/lib/tag-input/index.tsx index 80ac97716..d71194aca 100644 --- a/lib/tag-input/index.tsx +++ b/lib/tag-input/index.tsx @@ -1,30 +1,42 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { + ClipboardEvent, + Component, + CompositionEvent, + KeyboardEvent, + RefObject, +} from 'react'; import { get, identity, invoke, noop } from 'lodash'; +import * as T from '../types'; + const KEY_TAB = 9; const KEY_ENTER = 13; const KEY_RIGHT = 39; const KEY_COMMA = 188; -const startsWith = prefix => text => +const startsWith = (prefix: string) => (text: string): boolean => text .trim() .toLowerCase() .startsWith(prefix.trim().toLowerCase()); -export class TagInput extends Component { - static displayName = 'TagInput'; +type OwnProps = { + inputRef: (ref: RefObject) => any; + onChange: (tagName: string, callback: () => any) => any; + onSelect: (tagName: string) => any; + storeFocusInput: (focusSetter: () => any) => any; + storeHasFocus: (focusGetter: () => boolean) => any; + tagNames: T.TagName[]; + value: string; +}; - static propTypes = { - inputRef: PropTypes.func, - onChange: PropTypes.func, - onSelect: PropTypes.func, - storeFocusInput: PropTypes.func, - storeHasFocus: PropTypes.func, - tagNames: PropTypes.arrayOf(PropTypes.string).isRequired, - value: PropTypes.string.isRequired, - }; +type Props = OwnProps; + +export class TagInput extends Component { + inputField?: RefObject | null; + inputObserver?: MutationObserver; + + static displayName = 'TagInput'; static defaultProps = { inputRef: identity, @@ -63,7 +75,7 @@ export class TagInput extends Component { this.inputObserver.disconnect(); } - completeSuggestion = (andThen = identity) => { + completeSuggestion = (andThen: (...args: any[]) => any = identity) => { const { onChange, tagNames, value } = this.props; if (!value.length) { @@ -98,7 +110,7 @@ export class TagInput extends Component { hasFocus = () => document.activeElement === this.inputField; - interceptKeys = event => + interceptKeys = (event: KeyboardEvent) => invoke( { [KEY_ENTER]: this.submitTag, @@ -110,7 +122,7 @@ export class TagInput extends Component { event ); - interceptRightArrow = event => { + interceptRightArrow = (event: KeyboardEvent) => { const { value } = this.props; // if we aren't already at the right-most extreme @@ -127,14 +139,14 @@ export class TagInput extends Component { event.stopPropagation(); }; - interceptTabPress = event => { + interceptTabPress = (event: KeyboardEvent) => { this.completeSuggestion(this.submitTag); event.preventDefault(); event.stopPropagation(); }; - onInputMutation = mutationList => { + onInputMutation = (mutationList: MutationRecord[]) => { mutationList.forEach(mutation => { let value = get(mutation, 'target.data', ''); @@ -146,7 +158,7 @@ export class TagInput extends Component { }); }; - onInput = value => { + onInput = (value: string) => { if (this.state.isComposing) { return; } @@ -154,12 +166,12 @@ export class TagInput extends Component { this.props.onChange(value.trim(), this.focusInput); }; - onCompositionEnd = e => { + onCompositionEnd = (e: CompositionEvent) => { const value = e.target.textContent; this.setState({ isComposing: false }, () => this.onInput(value)); }; - removePastedFormatting = event => { + removePastedFormatting = (event: ClipboardEvent) => { let clipboardText; if (get(event, 'clipboardData.getData')) { @@ -174,7 +186,7 @@ export class TagInput extends Component { event.stopPropagation(); }; - storeInput = ref => { + storeInput = (ref: RefObject | null) => { this.inputField = ref; this.props.inputRef(ref); invoke( @@ -186,7 +198,7 @@ export class TagInput extends Component { ); }; - submitTag = event => { + submitTag = (event?: KeyboardEvent) => { const { onSelect, value } = this.props; value.trim().length && onSelect(value.trim()); diff --git a/lib/tag-list/index.tsx b/lib/tag-list/index.tsx index db6f2effe..d55b6432a 100644 --- a/lib/tag-list/index.tsx +++ b/lib/tag-list/index.tsx @@ -1,5 +1,4 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, FocusEvent, MouseEvent } from 'react'; import { connect } from 'react-redux'; import classNames from 'classnames'; import PanelTitle from '../components/panel-title'; @@ -11,26 +10,37 @@ import { renameTag, reorderTags, trashTag } from '../state/domain/tags'; import { toggleTagEditing } from '../state/ui/actions'; import analytics from '../analytics'; +import * as S from '../state'; +import * as T from '../types'; + const { selectTagAndSelectFirstNote } = appState.actionCreators; -export class TagList extends Component { - static displayName = 'TagList'; +type StateProps = { + editingTags: boolean; + tags: T.TagEntity[] | null; + selectedTag?: T.TagEntity; +}; - static propTypes = { - onSelectTag: PropTypes.func.isRequired, - onEditTags: PropTypes.func.isRequired, - renameTag: PropTypes.func.isRequired, - reorderTags: PropTypes.func.isRequired, - selectedTag: PropTypes.object, - tags: PropTypes.array.isRequired, - trashTag: PropTypes.func.isRequired, - }; +type DispatchProps = { + onEditTags: () => any; + onSelectTag: (tag: T.TagEntity) => any; + renameTag: (args: { tag: T.TagEntity; name: T.TagName }) => any; + reorderTags: (args: { tags: T.TagEntity[] }) => any; + trashTag: (args: { tag: T.TagEntity }) => any; +}; + +type Props = StateProps & DispatchProps; + +export class TagList extends Component { + static displayName = 'TagList'; - renderItem = tag => { + renderItem = (tag: T.TagEntity) => { const { editingTags, selectedTag } = this.props; const isSelected = tag.data.name === get(selectedTag, 'data.name', ''); - const handleRenameTag = ({ target: { value } }) => + const handleRenameTag = ({ + target: { value }, + }: FocusEvent) => this.props.renameTag({ tag, name: value }); return ( @@ -44,9 +54,9 @@ export class TagList extends Component { ); }; - onReorderTags = tags => this.props.reorderTags({ tags }); + onReorderTags = (tags: T.TagEntity[]) => this.props.reorderTags({ tags }); - onSelectTag = (tag, event) => { + onSelectTag = (tag: T.TagEntity, event: MouseEvent) => { if (!this.props.editingTags) { event.preventDefault(); event.currentTarget.blur(); @@ -54,7 +64,7 @@ export class TagList extends Component { } }; - onTrashTag = tag => { + onTrashTag = (tag: T.TagEntity) => { this.props.trashTag({ tag }); analytics.tracks.recordEvent('list_tag_deleted'); }; @@ -73,7 +83,7 @@ export class TagList extends Component { {tags.length > 0 && (