refactor: repo (#1070)

This commit is contained in:
Mo
2022-06-07 07:18:41 -05:00
committed by GitHub
parent 4c65784421
commit f4ef63693c
1102 changed files with 5786 additions and 3366 deletions

View File

@@ -0,0 +1,9 @@
import { MILLISECONDS_IN_A_DAY } from '@/Constants/Constants'
export const calculateDifferenceBetweenDatesInDays = (firstDate: Date, secondDate: Date) => {
const firstDateAsUTCMilliseconds = Date.UTC(firstDate.getFullYear(), firstDate.getMonth(), firstDate.getDate())
const secondDateAsUTCMilliseconds = Date.UTC(secondDate.getFullYear(), secondDate.getMonth(), secondDate.getDate())
return Math.round((firstDateAsUTCMilliseconds - secondDateAsUTCMilliseconds) / MILLISECONDS_IN_A_DAY)
}

View File

@@ -0,0 +1,64 @@
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER } from '@/Constants/Constants'
export type SubmenuStyle = {
top?: number | 'auto'
right?: number | 'auto'
bottom: number | 'auto'
left?: number | 'auto'
visibility?: 'hidden' | 'visible'
maxHeight: number | 'auto'
}
export const calculateSubmenuStyle = (
button: HTMLButtonElement | null,
menu?: HTMLDivElement | HTMLMenuElement | null,
): SubmenuStyle | undefined => {
const defaultFontSize = window.getComputedStyle(document.documentElement).fontSize
const maxChangeEditorMenuSize = parseFloat(defaultFontSize) * MAX_MENU_SIZE_MULTIPLIER
const { clientWidth, clientHeight } = document.documentElement
const buttonRect = button?.getBoundingClientRect()
const buttonParentRect = button?.parentElement?.getBoundingClientRect()
const menuBoundingRect = menu?.getBoundingClientRect()
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
const footerHeightInPx = footerElementRect?.height ?? 0
let position: SubmenuStyle = {
bottom: 'auto',
maxHeight: 'auto',
}
if (buttonRect && buttonParentRect) {
let positionBottom = clientHeight - buttonRect.bottom - buttonRect.height / 2
if (positionBottom < footerHeightInPx) {
positionBottom = footerHeightInPx + MENU_MARGIN_FROM_APP_BORDER
}
position = {
bottom: positionBottom,
visibility: 'hidden',
maxHeight: 'auto',
}
if (buttonRect.right + maxChangeEditorMenuSize > clientWidth) {
position.right = clientWidth - buttonRect.left
} else {
position.left = buttonRect.right
}
}
if (menuBoundingRect?.height && buttonRect && position.bottom !== 'auto') {
position.visibility = 'visible'
if (menuBoundingRect.y < MENU_MARGIN_FROM_APP_BORDER) {
position.bottom = position.bottom + menuBoundingRect.y - MENU_MARGIN_FROM_APP_BORDER * 2
}
if (footerElementRect && menuBoundingRect.height > footerElementRect.y) {
position.bottom = footerElementRect.height + MENU_MARGIN_FROM_APP_BORDER
position.maxHeight = clientHeight - footerElementRect.height - MENU_MARGIN_FROM_APP_BORDER * 2
}
}
return position
}

View File

@@ -0,0 +1,12 @@
export const concatenateUint8Arrays = (arrays: Uint8Array[]) => {
const totalLength = arrays.map((array) => array.length).reduce((prev, next) => prev + next, 0)
const concatenatedArray = new Uint8Array(totalLength)
let offset = 0
arrays.forEach((array) => {
concatenatedArray.set(array, offset)
offset += array.length
})
return concatenatedArray
}

View File

@@ -0,0 +1,31 @@
import { WebApplication } from '@/Application/Application'
const isBackupRelatedFile = (item: DataTransferItem, application: WebApplication): boolean => {
const fileName = item.getAsFile()?.name || ''
const isBackupMetadataFile = application.files.isFileNameFileBackupRelated(fileName) !== false
return isBackupMetadataFile
}
export const isHandlingFileDrag = (event: DragEvent, application: WebApplication) => {
const items = event.dataTransfer?.items
if (!items) {
return false
}
return Array.from(items).some((item) => {
return item.kind === 'file' && !isBackupRelatedFile(item, application)
})
}
export const isHandlingBackupDrag = (event: DragEvent, application: WebApplication) => {
const items = event.dataTransfer?.items
if (!items) {
return false
}
return Array.from(items).every((item) => {
return item.kind === 'file' && isBackupRelatedFile(item, application)
})
}

View File

@@ -0,0 +1,5 @@
import { dateToLocalizedString } from '@standardnotes/snjs/'
export const formatLastSyncDate = (lastUpdatedDate: Date) => {
return dateToLocalizedString(lastUpdatedDate)
}

View File

@@ -0,0 +1,28 @@
/**
* source: https://github.com/juliangruber/is-mobile
*
* (MIT)
* Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
const mobileRE =
/(android|bb\d+|meego).+mobile|armv7l|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series[46]0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i
const tabletRE = /android|ipad|playbook|silk/i
export type Opts = {
tablet?: boolean
}
export const isMobile = (opts: Opts = {}) => {
const ua = navigator.userAgent || navigator.vendor
if (typeof ua !== 'string') {
return false
}
return mobileRE.test(ua) || (!!opts.tablet && tabletRE.test(ua))
}

View File

@@ -0,0 +1,13 @@
import { SNApplication } from '@standardnotes/snjs'
export function openSubscriptionDashboard(application: SNApplication): void {
application
.getNewSubscriptionToken()
.then((token) => {
if (!token) {
return
}
window.open(`${window.dashboardUrl}?subscription_token=${token}`)
})
.catch(console.error)
}

View File

@@ -0,0 +1,14 @@
import { ThemeItem } from '@/Components/QuickSettingsMenu/ThemeItem'
export const sortThemes = (a: ThemeItem, b: ThemeItem) => {
const aIsLayerable = a.component?.isLayerable()
const bIsLayerable = b.component?.isLayerable()
if (aIsLayerable && !bIsLayerable) {
return 1
} else if (!aIsLayerable && bIsLayerable) {
return -1
} else {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1
}
}

View File

@@ -0,0 +1,61 @@
import { getIndexOfQueryInString, splitQueryInString, splitRangeWithinString } from './StringUtils'
describe('string utils', () => {
describe('splitRangeWithinString', () => {
it('should return whole string if range is invalid or out of bounds', () => {
const string = 'test-string'
const outOfBoundsStartResult = splitRangeWithinString(string, 15, 0)
expect(outOfBoundsStartResult).toStrictEqual(['test-string'])
const outOfBoundsEndResult = splitRangeWithinString(string, 0, -15)
expect(outOfBoundsEndResult).toStrictEqual(['test-string'])
const invalidRangeResult = splitRangeWithinString(string, 15, 0)
expect(invalidRangeResult).toStrictEqual(['test-string'])
})
it('should return split string if range is valid', () => {
const string = 'test-string'
const case1 = splitRangeWithinString(string, 0, 3)
expect(case1).toStrictEqual(['tes', 't-string'])
const case2 = splitRangeWithinString(string, 2, 6)
expect(case2).toStrictEqual(['te', 'st-s', 'tring'])
const case3 = splitRangeWithinString(string, 4, 9)
expect(case3).toStrictEqual(['test', '-stri', 'ng'])
})
})
describe('getIndexOfQueryInString', () => {
it('should get correct index of query in string', () => {
const string = 'tEsT-sTrInG'
const indexOfQuery1 = getIndexOfQueryInString(string, 'tRi')
expect(indexOfQuery1).toBe(6)
const indexOfQuery2 = getIndexOfQueryInString(string, 'StR')
expect(indexOfQuery2).toBe(5)
const indexOfQuery3 = getIndexOfQueryInString(string, 'stringUtils')
expect(indexOfQuery3).toBe(-1)
})
})
describe('splitQueryInString', () => {
it('should split string if it includes the query', () => {
const string = 'TeSt-StRiNg'
const query1Result = splitQueryInString(string, 'T-sTr')
expect(query1Result).toStrictEqual(['TeS', 't-StR', 'iNg'])
const query2Result = splitQueryInString(string, 'InG')
expect(query2Result).toStrictEqual(['TeSt-StR', 'iNg'])
const query3Result = splitQueryInString(string, 'invalid query')
expect(query3Result).toStrictEqual(['TeSt-StRiNg'])
})
})
})

View File

@@ -0,0 +1,27 @@
export const splitRangeWithinString = (string: string, start: number, end: number) => {
const isStartOutOfBounds = start > string.length || start < 0
const isEndOutOfBounds = end > string.length || end < 0
const isInvalidRange = start > end
if (isStartOutOfBounds || isEndOutOfBounds || isInvalidRange) {
return [string]
} else {
return [string.slice(0, start), string.slice(start, end), string.slice(end)].filter((slice) => slice.length > 0)
}
}
export const getIndexOfQueryInString = (string: string, query: string) => {
const lowercasedTitle = string.toLowerCase()
const lowercasedQuery = query.toLowerCase()
return lowercasedTitle.indexOf(lowercasedQuery)
}
export const splitQueryInString = (string: string, query: string) => {
const indexOfQueryInTitle = getIndexOfQueryInString(string, query)
if (indexOfQueryInTitle < 0) {
return [string]
}
return splitRangeWithinString(string, indexOfQueryInTitle, indexOfQueryInTitle + query.length)
}

View File

@@ -0,0 +1,172 @@
import { Platform, platformFromString } from '@standardnotes/snjs'
import { IsDesktopPlatform, IsWebPlatform } from '@/Constants/Version'
import { EMAIL_REGEX } from '../Constants/Constants'
export { isMobile } from './IsMobile'
declare const process: {
env: {
NODE_ENV: string | null | undefined
}
}
export const isDev = process.env.NODE_ENV === 'development'
export function getPlatformString() {
try {
const platform = navigator.platform.toLowerCase()
let trimmed = ''
if (platform.includes('mac')) {
trimmed = 'mac'
} else if (platform.includes('win')) {
trimmed = 'windows'
} else if (platform.includes('linux')) {
trimmed = 'linux'
} else {
/** Treat other platforms as linux */
trimmed = 'linux'
}
return trimmed + (isDesktopApplication() ? '-desktop' : '-web')
} catch (e) {
return 'linux-web'
}
}
export function getPlatform(): Platform {
return platformFromString(getPlatformString())
}
export function isSameDay(dateA: Date, dateB: Date): boolean {
return (
dateA.getFullYear() === dateB.getFullYear() &&
dateA.getMonth() === dateB.getMonth() &&
dateA.getDate() === dateB.getDate()
)
}
/** Via https://davidwalsh.name/javascript-debounce-function */
export function debounce(this: any, func: any, wait: number, immediate = false) {
let timeout: NodeJS.Timeout | null
return () => {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const context = this
// eslint-disable-next-line prefer-rest-params
const args = arguments
const later = function () {
timeout = null
if (!immediate) {
func.apply(context, args)
}
}
const callNow = immediate && !timeout
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(later, wait)
if (callNow) {
func.apply(context, args)
}
}
}
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
// eslint-disable-next-line no-extend-native
Object.defineProperty(Array.prototype, 'includes', {
value: function (searchElement: any, fromIndex: number) {
if (this == null) {
throw new TypeError('"this" is null or not defined')
}
// 1. Let O be ? ToObject(this value).
const o = Object(this)
// 2. Let len be ? ToLength(? Get(O, "length")).
const len = o.length >>> 0
// 3. If len is 0, return false.
if (len === 0) {
return false
}
// 4. Let n be ? ToInteger(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
const n = fromIndex | 0
// 5. If n ≥ 0, then
// a. Let k be n.
// 6. Else n < 0,
// a. Let k be len + n.
// b. If k < 0, let k be 0.
let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0)
function sameValueZero(x: number, y: number) {
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y))
}
// 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(searchElement, elementK) is true, return true.
if (sameValueZero(o[k], searchElement)) {
return true
}
// c. Increase k by 1.
k++
}
// 8. Return false
return false
},
})
}
export async function preventRefreshing(message: string, operation: () => Promise<void> | void) {
const onBeforeUnload = window.onbeforeunload
try {
window.onbeforeunload = () => message
await operation()
} finally {
window.onbeforeunload = onBeforeUnload
}
}
if (!IsWebPlatform && !IsDesktopPlatform) {
throw Error('Neither __WEB__ nor __DESKTOP__ is true. Check your configuration files.')
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function destroyAllObjectProperties(object: any): void {
for (const prop of Object.getOwnPropertyNames(object)) {
try {
delete object[prop]
// eslint-disable-next-line no-empty
} catch (error) {}
}
}
export function isDesktopApplication() {
return IsDesktopPlatform
}
export function getDesktopVersion() {
return window.electronAppVersion
}
export const isEmailValid = (email: string): boolean => {
return EMAIL_REGEX.test(email)
}
export const getWindowUrlParams = (): URLSearchParams => {
return new URLSearchParams(window.location.search)
}
export const openInNewTab = (url: string) => {
const newWindow = window.open(url, '_blank', 'noopener,noreferrer')
if (newWindow) {
newWindow.opener = null
}
}
export const convertStringifiedBooleanToBoolean = (value: string) => {
return value !== 'false'
}

View File

@@ -0,0 +1,5 @@
export * from './CalculateSubmenuStyle'
export * from './ConcatenateUint8Arrays'
export * from './IsMobile'
export * from './StringUtils'
export * from './Utils'