diff --git a/.yarn/cache/contactjs-npm-2.1.5-c7bb4524ec-5be3d66835.zip b/.yarn/cache/contactjs-npm-2.1.5-c7bb4524ec-5be3d66835.zip new file mode 100644 index 000000000..c7a52e7b4 Binary files /dev/null and b/.yarn/cache/contactjs-npm-2.1.5-c7bb4524ec-5be3d66835.zip differ diff --git a/packages/web/package.json b/packages/web/package.json index 2cf722d38..0ee40fcca 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -109,6 +109,7 @@ "dependencies": { "@ariakit/react": "^0.1.2", "@lexical/headless": "0.10.0", - "@radix-ui/react-slot": "^1.0.1" + "@radix-ui/react-slot": "^1.0.1", + "contactjs": "2.1.5" } } diff --git a/packages/web/src/javascripts/Components/ContentListView/ListItemTags.tsx b/packages/web/src/javascripts/Components/ContentListView/ListItemTags.tsx index 4f59c5c0c..84808eac3 100644 --- a/packages/web/src/javascripts/Components/ContentListView/ListItemTags.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/ListItemTags.tsx @@ -14,7 +14,7 @@ const ListItemTags: FunctionComponent = ({ hideTags, tags }) => { } return ( -
+
{tags.map((tag) => ( > = ({
node.clientHeight || node.scrollWidth > node.clientWidth) { - return node - } else { - return getScrollParent(node.parentElement) - } -} - -const supportsPassive = (() => { - let supportsPassive = false - try { - const opts = Object.defineProperty({}, 'passive', { - get: () => { - supportsPassive = true - }, - }) - window.addEventListener('test', null as never, opts) - window.removeEventListener('test', null as never, opts) - } catch (e) { - /* empty */ - } - return supportsPassive -})() export const usePaneSwipeGesture = ( direction: 'left' | 'right', @@ -40,18 +12,16 @@ export const usePaneSwipeGesture = ( ) => { const application = useApplication() - const underlayElementRef = useRef(null) + const overlayElementRef = useRef(null) const [element, setElement] = useState(null) const onSwipeEndRef = useStateRef(onSwipeEnd) const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm) - const [isEnabled, setIsEnabled] = useState(() => - application.getPreference(PrefKey.PaneGesturesEnabled, PrefDefaults[PrefKey.PaneGesturesEnabled]), - ) + const [isEnabled, setIsEnabled] = useState(() => application.getPreference(PrefKey.PaneGesturesEnabled, false)) useEffect(() => { return application.addSingleEventObserver(ApplicationEvent.PreferencesChanged, async () => { - setIsEnabled(application.getPreference(PrefKey.PaneGesturesEnabled, PrefDefaults[PrefKey.PaneGesturesEnabled])) + setIsEnabled(application.getPreference(PrefKey.PaneGesturesEnabled, false)) }) }, [application]) @@ -68,159 +38,126 @@ export const usePaneSwipeGesture = ( return } - underlayElementRef.current = element.parentElement?.querySelector(`[data-pane-underlay="${element.id}"]`) || null + const panRecognizer = new Pan(element, { + supportedDirections: direction === 'left' ? [Direction.Left] : [Direction.Right], + }) - let startX = 0 - let clientX = 0 - let closestScrollContainer: HTMLElement | null - let scrollContainerAxis: 'x' | 'y' | null = null - let canceled = false + const pointerListener = new PointerListener(element, { + supportedGestures: [panRecognizer], + }) - const TouchMoveThreshold = 15 - const SwipeFinishThreshold = 40 + TouchMoveThreshold + function onPan(e: unknown) { + const event = e as CustomEvent + if (!element) { + return + } - const scrollListener = () => { - canceled = true + const x = event.detail.global.deltaX + requestElementUpdate(x) } - const touchStartListener = (event: TouchEvent) => { - closestScrollContainer = getScrollParent(event.target as HTMLElement) - if (closestScrollContainer) { - closestScrollContainer.addEventListener('scroll', scrollListener) + let ticking = false - if (closestScrollContainer.scrollWidth > closestScrollContainer.clientWidth) { - scrollContainerAxis = 'x' + function onPanEnd(e: unknown) { + const event = e as CustomEvent + if (ticking) { + setTimeout(function () { + onPanEnd(event) + }, 100) + } else { + if (!element) { + return } - } else { - scrollContainerAxis = null - } - const touch = event.touches[0] - startX = touch.clientX + if (direction === 'right' && event.detail.global.deltaX > 40) { + onSwipeEndRef.current(element) + } else if (direction === 'left' && event.detail.global.deltaX < -40) { + onSwipeEndRef.current(element) + } else { + requestElementUpdate(0) + } - canceled = false - - element.style.willChange = 'transform' - } - - const updateElement = (x: number) => { - if (!underlayElementRef.current) { - const underlayElement = document.createElement('div') - underlayElement.style.position = 'fixed' - underlayElement.style.top = '0' - underlayElement.style.left = '0' - underlayElement.style.width = '100%' - underlayElement.style.height = '100%' - underlayElement.style.pointerEvents = 'none' - underlayElement.style.backgroundColor = '#000' - underlayElement.style.opacity = '0' - underlayElement.style.willChange = 'opacity' - underlayElement.setAttribute('role', 'presentation') - underlayElement.ariaHidden = 'true' - underlayElement.setAttribute('data-pane-underlay', element.id) - - element.before(underlayElement) - underlayElementRef.current = underlayElement - } - - element.animate( - [ - { - transform: `translate3d(${x}px, 0, 0)`, - }, - ], - { - duration: 0, - fill: 'forwards', - }, - ) - - const percent = Math.min(window.innerWidth / x / 10, 0.45) - underlayElementRef.current.animate([{ opacity: percent }], { - duration: 0, - fill: 'forwards', - }) - } - - const touchMoveListener = (event: TouchEvent) => { - if (scrollContainerAxis === 'x') { - return - } - - if (canceled) { - return - } - - const touch = event.touches[0] - clientX = touch.clientX - - const deltaX = clientX - startX - - if (Math.abs(deltaX) < TouchMoveThreshold) { - return - } - - if (closestScrollContainer) { - closestScrollContainer.style.touchAction = 'none' - } - - const x = - direction === 'right' ? Math.max(deltaX - TouchMoveThreshold, 0) : Math.min(deltaX + TouchMoveThreshold, 0) - - if (gesture === 'pan') { - updateElement(x) + if (overlayElementRef.current) { + overlayElementRef.current + .animate([{ opacity: 0 }], { + duration: 5, + fill: 'forwards', + }) + .finished.then(() => { + if (overlayElementRef.current) { + overlayElementRef.current.remove() + overlayElementRef.current = null + } + }) + .catch(console.error) + } } } - const touchEndListener = () => { - if (closestScrollContainer) { - closestScrollContainer.removeEventListener('scroll', scrollListener) - closestScrollContainer.style.touchAction = '' - } + function requestElementUpdate(x: number) { + if (!ticking) { + requestAnimationFrame(function () { + if (!element) { + return + } - if (canceled) { - updateElement(0) - return - } + if (!overlayElementRef.current) { + const overlayElement = document.createElement('div') + overlayElement.style.position = 'fixed' + overlayElement.style.top = '0' + overlayElement.style.left = '0' + overlayElement.style.width = '100%' + overlayElement.style.height = '100%' + overlayElement.style.pointerEvents = 'none' + overlayElement.style.backgroundColor = '#000' + overlayElement.style.opacity = '0' + overlayElement.style.willChange = 'opacity' - const deltaX = clientX - startX + element.before(overlayElement) + overlayElementRef.current = overlayElement + } - element.style.willChange = '' + const newLeft = direction === 'right' ? Math.max(x, 0) : Math.min(x, 0) + element.animate([{ transform: `translate3d(${newLeft}px,0,0)` }], { duration: 0, fill: 'forwards' }) - if ( - (direction === 'right' && deltaX > SwipeFinishThreshold) || - (direction === 'left' && deltaX < -SwipeFinishThreshold) - ) { - onSwipeEndRef.current(element) - } else { - updateElement(0) - } - - if (underlayElementRef.current) { - underlayElementRef.current - .animate([{ opacity: 0 }], { - easing: 'cubic-bezier(.36,.66,.04,1)', - duration: 500, + const percent = Math.min(window.innerWidth / newLeft / 10, 0.45) + overlayElementRef.current.animate([{ opacity: percent }], { + duration: 0, fill: 'forwards', }) - .finished.then(() => { - if (underlayElementRef.current) { - underlayElementRef.current.remove() - underlayElementRef.current = null - } - }) - .catch(console.error) + + ticking = false + }) + + ticking = true } } - element.addEventListener('touchstart', touchStartListener, supportsPassive ? { passive: true } : false) - element.addEventListener('touchmove', touchMoveListener, supportsPassive ? { passive: true } : false) - element.addEventListener('touchend', touchEndListener, supportsPassive ? { passive: true } : false) + if (gesture === 'pan') { + element.addEventListener('panleft', onPan) + element.addEventListener('panright', onPan) + element.addEventListener('panend', onPanEnd) + } else { + if (direction === 'left') { + element.addEventListener('swipeleft', onPanEnd) + } else { + element.addEventListener('swiperight', onPanEnd) + } + } return () => { - element.removeEventListener('touchstart', touchStartListener) - element.removeEventListener('touchmove', touchMoveListener) - element.removeEventListener('touchend', touchEndListener) + pointerListener.destroy() + if (gesture === 'pan') { + element.removeEventListener('panleft', onPan) + element.removeEventListener('panright', onPan) + element.removeEventListener('panend', onPanEnd) + } else { + if (direction === 'left') { + element.removeEventListener('swipeleft', onPanEnd) + } else { + element.removeEventListener('swiperight', onPanEnd) + } + } } }, [direction, element, gesture, isMobileScreen, onSwipeEndRef, isEnabled]) diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/General/Labs/Labs.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/General/Labs/Labs.tsx index bafda97bc..cab02a1cc 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/General/Labs/Labs.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/General/Labs/Labs.tsx @@ -8,7 +8,6 @@ import PreferencesSegment from '../../../PreferencesComponents/PreferencesSegmen import LabsFeature from './LabsFeature' import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator' import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery' -import { PrefDefaults } from '@/Constants/PrefDefaults' type ExperimentalFeatureItem = { identifier: FeatureIdentifier @@ -31,13 +30,11 @@ const LabsPane: FunctionComponent = ({ application }) => { const [experimentalFeatures, setExperimentalFeatures] = useState([]) const [isPaneGesturesEnabled, setIsPaneGesturesEnabled] = useState(() => - application.getPreference(PrefKey.PaneGesturesEnabled, PrefDefaults[PrefKey.PaneGesturesEnabled]), + application.getPreference(PrefKey.PaneGesturesEnabled, false), ) useEffect(() => { return application.addSingleEventObserver(ApplicationEvent.PreferencesChanged, async () => { - setIsPaneGesturesEnabled( - application.getPreference(PrefKey.PaneGesturesEnabled, PrefDefaults[PrefKey.PaneGesturesEnabled]), - ) + setIsPaneGesturesEnabled(application.getPreference(PrefKey.PaneGesturesEnabled, false)) }) }, [application]) diff --git a/packages/web/src/javascripts/Constants/PrefDefaults.ts b/packages/web/src/javascripts/Constants/PrefDefaults.ts index 22afc8246..054efedc3 100644 --- a/packages/web/src/javascripts/Constants/PrefDefaults.ts +++ b/packages/web/src/javascripts/Constants/PrefDefaults.ts @@ -29,5 +29,4 @@ export const PrefDefaults = { [PrefKey.CustomNoteTitleFormat]: 'YYYY-MM-DD [at] hh:mm A', [PrefKey.UpdateSavingStatusIndicator]: true, [PrefKey.DarkMode]: false, - [PrefKey.PaneGesturesEnabled]: true, } as const diff --git a/packages/web/src/javascripts/Controllers/Moments/PhotoRecorder.ts b/packages/web/src/javascripts/Controllers/Moments/PhotoRecorder.ts index 0e895a9fc..f50e2c8b5 100644 --- a/packages/web/src/javascripts/Controllers/Moments/PhotoRecorder.ts +++ b/packages/web/src/javascripts/Controllers/Moments/PhotoRecorder.ts @@ -12,9 +12,6 @@ export class PhotoRecorder { constructor() {} public static async isSupported(): Promise { - if (!navigator.mediaDevices) { - return false - } const devices = await navigator.mediaDevices.enumerateDevices() const hasCamera = devices.some((device) => device.kind === 'videoinput') return hasCamera diff --git a/packages/web/web.webpack.config.js b/packages/web/web.webpack.config.js index 22df5f748..55114b91c 100644 --- a/packages/web/web.webpack.config.js +++ b/packages/web/web.webpack.config.js @@ -76,7 +76,8 @@ module.exports = (env) => { * Exclude all node_modules, except for those we need to run through our babel rules because * they may contain class properties and other ES6+ syntax. */ - exclude: /node_modules\/(?!(@standardnotes\/common|@standardnotes\/domain-core|webextension-polyfill))/, + exclude: + /node_modules\/(?!(@standardnotes\/common|@standardnotes\/domain-core|contactjs|webextension-polyfill))/, use: [ 'babel-loader', { diff --git a/yarn.lock b/yarn.lock index 4c2f9eae7..76ac1092f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5547,6 +5547,7 @@ __metadata: autoprefixer: ^10.4.13 babel-loader: ^9.1.0 circular-dependency-plugin: ^5.2.2 + contactjs: 2.1.5 copy-webpack-plugin: ^11.0.0 css-loader: "*" dayjs: ^1.11.7 @@ -9630,6 +9631,13 @@ __metadata: languageName: node linkType: hard +"contactjs@npm:2.1.5": + version: 2.1.5 + resolution: "contactjs@npm:2.1.5" + checksum: 5be3d66835e5a78a16abe6cdbf0c2f4a44471086e313ef8550c6747026cca9e7ff5144fdaaf6b4307cb296baf0fd394ca63758354e58099f1fde3c6756a390c3 + languageName: node + linkType: hard + "content-disposition@npm:0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4"