Compare commits

...
Author SHA1 Message Date
StandardNotes CI 1f3e741811 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-08-08 09:30:06 +00:00
Aman Harwara 887402f75f fix: viewport height on mobile Chrome & Safari (#1376) 2022-08-08 14:32:16 +05:30
StandardNotes CI d533fb78e0 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-08-08 07:35:45 +00:00
Vardan Hakobyan f555fbcaaa feat: tablet responsiveness (#1369)
* feat: "Notes" column responsiveness for tablets

* feat: ability to expand/collapse the navigation column, move some styles to tailwind

* feat: ability to expand/collapse the navigation column, move some styles to tailwind

* feat: icon for collapsing/expanding navigation column, style improvements

* fix: restore vertical panels resizability on large screens

* feat: tags/folders section for collapsed state

* feat: notes subsection styling in navigation section

* feat: hide rename & delete links of smart views when in collapsed mode

* fix: show notes count in bold when in collapsed mode

* fix: better padding for folders in collapsed state

* fix: smaller left padding for collapsed case

* fix: better view of expand/collapse control

* fix: hide search bar when collapsed

* fix: adjust position of "expand" control when in collapsed state

* chore: remove comments

* refactor: use `classNames` util when applying conditional css classes

* chore: shorter class names syntax

* chore: shorter syntax for conditional classes

* fix: better way of setting background color with opacity

* fix: use variable colors according to SN themes

* chore: move "colors" from "extend" section and thus override Tailwind's colors (so only SN colors will exist)

* fix: correct hover style in collapsed mode

* fix: correct upper boundary for extra small screens
2022-08-08 11:07:15 +04:00
Karol Sójko 8332a70384 fix(web): cannonical link rel 2022-08-08 09:04:39 +02:00
StandardNotes CI 50e85cac15 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-08-06 05:20:47 +00:00
Aman Harwara f2369c7555 chore: remove HtmlWebpackPlugin from dev webpack config (#1377) 2022-08-06 06:51:39 +02:00
25 changed files with 445 additions and 303 deletions
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.23.89](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-08)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.88](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-08)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.87](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-06)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.86](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-05)
**Note:** Version bump only for package @standardnotes/desktop
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.23.86",
"version": "3.23.89",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.31.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-08)
**Note:** Version bump only for package @standardnotes/mobile
## [3.31.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-08)
**Note:** Version bump only for package @standardnotes/mobile
## [3.31.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-06)
**Note:** Version bump only for package @standardnotes/mobile
## [3.31.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-05)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.31.2",
"version": "3.31.5",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-08)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-08)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-06)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-05)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.3.8",
"version": "1.3.11",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+20
View File
@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.44.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-08)
### Bug Fixes
* viewport height on mobile Chrome & Safari ([#1376](https://github.com/standardnotes/app/issues/1376)) ([887402f](https://github.com/standardnotes/app/commit/887402f75fe7d03a376e42446661f6b43a2c372c))
# [3.44.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-08)
### Bug Fixes
* **web:** cannonical link rel ([8332a70](https://github.com/standardnotes/app/commit/8332a7038405378faf4bdb7c5740d543758d69b1))
### Features
* tablet responsiveness ([#1369](https://github.com/standardnotes/app/issues/1369)) ([f555fbc](https://github.com/standardnotes/app/commit/f555fbcaaab1203c8460e7a69c3d7723d09f45d7))
## [3.43.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-06)
**Note:** Version bump only for package @standardnotes/web
# [3.43.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-08-05)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/web",
"version": "3.43.0",
"version": "3.44.1",
"license": "AGPL-3.0-or-later",
"main": "dist/app.js",
"author": "Standard Notes.",
+1 -1
View File
@@ -5,7 +5,7 @@
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<link rel="canonical" href="https://app-static.standardnotes.com" />
<link rel="canonical" href="https://app.standardnotes.com" />
<link href="favicon/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180"></link>
<link href="favicon/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"></link>
+10
View File
@@ -39,6 +39,10 @@ const getKey = () => {
const RootId = 'app-group-root'
const setViewportHeight = () => {
document.documentElement.style.setProperty('--viewport-height', `${window.innerHeight}px`)
}
const startApplication: StartApplication = async function startApplication(
defaultSyncServerHost: string,
device: WebOrDesktopDevice,
@@ -54,6 +58,8 @@ const startApplication: StartApplication = async function startApplication(
const rootElement = document.getElementById(RootId) as HTMLElement
root.unmount()
rootElement.remove()
window.removeEventListener('resize', setViewportHeight)
window.removeEventListener('orientationchange', setViewportHeight)
renderApp()
}
@@ -63,6 +69,10 @@ const startApplication: StartApplication = async function startApplication(
const appendedRootNode = document.body.appendChild(rootElement)
root = createRoot(appendedRootNode)
setViewportHeight()
window.addEventListener('resize', setViewportHeight)
window.addEventListener('orientationchange', setViewportHeight)
root.render(
<ApplicationGroupView
key={getKey()}
@@ -172,7 +172,7 @@ const ContentListView: FunctionComponent<Props> = ({
return (
<div
id="items-column"
className="sn-component section app-column app-column-second flex flex-col border-b border-solid border-border"
className="sn-component section app-column flex flex-col border-b border-solid border-border xl:w-87.5 xsm-only:!w-full sm-only:!w-full md-only:!w-52 lg-only:!w-52"
aria-label={'Notes & Files'}
ref={itemsViewPanelRef}
>
@@ -10,6 +10,7 @@ import SearchBar from '@/Components/SearchBar/SearchBar'
import ResponsivePaneContent from '@/Components/ResponsivePane/ResponsivePaneContent'
import { AppPaneId } from '@/Components/ResponsivePane/AppPaneMetadata'
import { classNames } from '@/Utils/ConcatenateClassNames'
import Icon from '@/Components/Icon/Icon'
type Props = {
application: WebApplication
@@ -19,6 +20,7 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
const viewControllerManager = useMemo(() => application.getViewControllerManager(), [application])
const ref = useRef<HTMLDivElement>(null)
const [panelWidth, setPanelWidth] = useState<number>(0)
const [isPanelExpanded, setIsPanelExpanded] = useState(true)
useEffect(() => {
const removeObserver = application.addEventObserver(async () => {
@@ -47,19 +49,39 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
}, [viewControllerManager])
return (
<div id="navigation" className="sn-component section app-column app-column-first" ref={ref}>
<div
id="navigation"
className={classNames(
'sn-component section app-column xl:w-[220px] xsm-only:!w-full sm-only:!w-full md-only:transition-width lg-only:transition-width',
isPanelExpanded ? 'md-only:!w-[220px] lg-only:!w-[220px]' : 'md-only:!w-18 lg-only:!w-18',
)}
ref={ref}
>
<ResponsivePaneContent paneId={AppPaneId.Navigation} contentElementId="navigation-content">
<SearchBar
itemListController={viewControllerManager.itemListController}
searchOptionsController={viewControllerManager.searchOptionsController}
selectedViewTitle={viewControllerManager.navigationController.selected?.title}
/>
<div className="section-title-bar">
<div className="section-title-bar-header">
<div className="title text-sm">
<span className="font-bold">Views</span>
{isPanelExpanded && (
<SearchBar
itemListController={viewControllerManager.itemListController}
searchOptionsController={viewControllerManager.searchOptionsController}
selectedViewTitle={viewControllerManager.navigationController.selected?.title}
/>
)}
<div className={'flex justify-end'}>
<div className={classNames('section-title-bar block w-full xl:block', !isPanelExpanded && 'hidden')}>
<div className="section-title-bar-header">
<div className="title text-sm">
<span className="font-bold">Views</span>
</div>
</div>
</div>
<div
className={classNames(
'hidden items-end self-end rounded-full rounded-tr-none rounded-br-none bg-default p-1 text-foreground md:flex xl:hidden',
isPanelExpanded ? 'mb-1 w-fit' : 'mt-4.5 mb-3 ml-3 w-full',
)}
onClick={() => setIsPanelExpanded(!isPanelExpanded)}
>
<Icon type="chevron-down" className={isPanelExpanded ? 'rotate-90' : '-rotate-90'} />
</div>
</div>
<div
className={classNames(
@@ -68,8 +90,8 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
'md:hover:[overflow-y:_overlay]',
)}
>
<SmartViewsSection viewControllerManager={viewControllerManager} />
<TagsSection viewControllerManager={viewControllerManager} />
<SmartViewsSection viewControllerManager={viewControllerManager} isCollapsed={!isPanelExpanded} />
<TagsSection viewControllerManager={viewControllerManager} isCollapsed={!isPanelExpanded} />
</div>
</ResponsivePaneContent>
{ref.current && (
@@ -5,9 +5,10 @@ import SmartViewsListItem from './SmartViewsListItem'
type Props = {
viewControllerManager: ViewControllerManager
isCollapsed: boolean
}
const SmartViewsList: FunctionComponent<Props> = ({ viewControllerManager }: Props) => {
const SmartViewsList: FunctionComponent<Props> = ({ viewControllerManager, isCollapsed }: Props) => {
const allViews = viewControllerManager.navigationController.smartViews
return (
@@ -19,6 +20,7 @@ const SmartViewsList: FunctionComponent<Props> = ({ viewControllerManager }: Pro
view={view}
tagsState={viewControllerManager.navigationController}
features={viewControllerManager.featuresController}
isCollapsed={isCollapsed}
/>
)
})}
@@ -15,11 +15,13 @@ import {
} from 'react'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { classNames } from '@/Utils/ConcatenateClassNames'
type Props = {
view: SmartView
tagsState: NavigationController
features: FeaturesController
isCollapsed: boolean
}
const PADDING_BASE_PX = 14
@@ -45,7 +47,7 @@ const smartViewIconType = (view: SmartView, isSelected: boolean): IconType => {
return 'hashtag'
}
const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState, isCollapsed }) => {
const { toggleAppPane } = useResponsiveAppPane()
const [title, setTitle] = useState(view.title || '')
@@ -111,18 +113,30 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
return (
<>
<div
className={`tag ${isSelected ? 'selected' : ''} ${isFaded ? 'opacity-50' : ''}`}
className={classNames(
'tag',
isSelected && 'selected',
isFaded && 'opacity-50',
isCollapsed && '!bg-transparent',
)}
onClick={selectCurrentTag}
style={{
paddingLeft: `${level * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`,
}}
>
<div className="tag-info">
<div className={'tag-icon mr-2'}>
<Icon type={iconType} className={`${isSelected ? 'text-info' : 'text-neutral'}`} />
<div className="tag-info relative">
<div
className={classNames(
isCollapsed
? `relative flex h-[40px] w-[40px] items-center justify-center
${isSelected ? 'transparent-info-color-background' : 'transparent-info-color-background-hover'}`
: 'tag-icon mr-2',
)}
>
<Icon type={iconType} className={isSelected ? 'text-info' : 'text-neutral'} />
</div>
<input
className={`title ${isEditing ? 'editing' : ''}`}
className={classNames('title', isEditing ? 'editing' : '', isCollapsed ? 'hidden' : 'block')}
disabled={!isEditing}
id={`react-tag-${view.uuid}`}
onBlur={onBlur}
@@ -132,10 +146,12 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
spellCheck={false}
ref={inputRef}
/>
<div className="count">{view.uuid === SystemViewId.AllNotes && tagsState.allNotesCount}</div>
<div className={classNames('count', isCollapsed ? 'absolute top-0 right-0' : '')}>
{view.uuid === SystemViewId.AllNotes && tagsState.allNotesCount}
</div>
</div>
{!isSystemView(view) && (
{!isSystemView(view) && !isCollapsed && (
<div className="meta">
{view.conflictOf && (
<div className="danger text-[0.625rem] font-bold">Conflicted Copy {view.conflictOf}</div>
@@ -5,12 +5,13 @@ import SmartViewsList from './SmartViewsList'
type Props = {
viewControllerManager: ViewControllerManager
isCollapsed: boolean
}
const SmartViewsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
const SmartViewsSection: FunctionComponent<Props> = ({ viewControllerManager, isCollapsed }) => {
return (
<section>
<SmartViewsList viewControllerManager={viewControllerManager} />
<SmartViewsList viewControllerManager={viewControllerManager} isCollapsed={isCollapsed} />
</section>
)
}
@@ -9,9 +9,10 @@ import { TagsListItem } from './TagsListItem'
type Props = {
viewControllerManager: ViewControllerManager
isCollapsed: boolean
}
const TagsList: FunctionComponent<Props> = ({ viewControllerManager }: Props) => {
const TagsList: FunctionComponent<Props> = ({ viewControllerManager, isCollapsed }: Props) => {
const tagsState = viewControllerManager.navigationController
const allTags = tagsState.allLocalRootTags
@@ -52,6 +53,7 @@ const TagsList: FunctionComponent<Props> = ({ viewControllerManager }: Props) =>
tagsState={tagsState}
features={viewControllerManager.featuresController}
onContextMenu={onContextMenu}
isCollapsed={isCollapsed}
/>
)
})}
@@ -22,6 +22,7 @@ import { useDrag, useDrop } from 'react-dnd'
import { DropItem, DropProps, ItemTypes } from './DragNDrop'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { classNames } from '@/Utils/ConcatenateClassNames'
type Props = {
tag: SNTag
@@ -29,273 +30,290 @@ type Props = {
features: FeaturesController
level: number
onContextMenu: (tag: SNTag, posX: number, posY: number) => void
isCollapsed: boolean
}
const PADDING_BASE_PX = 14
const PADDING_BASE_WHEN_COLLAPSED_PX = 6
const PADDING_PER_LEVEL_PX = 21
const PADDING_PER_LEVEL_WHEN_COLLAPSED_PX = 10
export const TagsListItem: FunctionComponent<Props> = observer(({ tag, features, tagsState, level, onContextMenu }) => {
const { toggleAppPane } = useResponsiveAppPane()
export const TagsListItem: FunctionComponent<Props> = observer(
({ tag, features, tagsState, level, onContextMenu, isCollapsed }) => {
const { toggleAppPane } = useResponsiveAppPane()
const [title, setTitle] = useState(tag.title || '')
const [subtagTitle, setSubtagTitle] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
const subtagInputRef = useRef<HTMLInputElement>(null)
const menuButtonRef = useRef<HTMLAnchorElement>(null)
const [title, setTitle] = useState(tag.title || '')
const [subtagTitle, setSubtagTitle] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
const subtagInputRef = useRef<HTMLInputElement>(null)
const menuButtonRef = useRef<HTMLAnchorElement>(null)
const isSelected = tagsState.selected === tag
const isEditing = tagsState.editingTag === tag
const isAddingSubtag = tagsState.addingSubtagTo === tag
const noteCounts = computed(() => tagsState.getNotesCount(tag))
const isSelected = tagsState.selected === tag
const isEditing = tagsState.editingTag === tag
const isAddingSubtag = tagsState.addingSubtagTo === tag
const noteCounts = computed(() => tagsState.getNotesCount(tag))
const childrenTags = computed(() => tagsState.getChildren(tag)).get()
const hasChildren = childrenTags.length > 0
const childrenTags = computed(() => tagsState.getChildren(tag)).get()
const hasChildren = childrenTags.length > 0
const hasFolders = features.hasFolders
const hasAtLeastOneFolder = tagsState.hasAtLeastOneFolder
const hasFolders = features.hasFolders
const hasAtLeastOneFolder = tagsState.hasAtLeastOneFolder
const premiumModal = usePremiumModal()
const premiumModal = usePremiumModal()
const [showChildren, setShowChildren] = useState(tag.expanded)
const [hadChildren, setHadChildren] = useState(hasChildren)
const [showChildren, setShowChildren] = useState(tag.expanded)
const [hadChildren, setHadChildren] = useState(hasChildren)
useEffect(() => {
if (!hadChildren && hasChildren) {
setShowChildren(true)
}
setHadChildren(hasChildren)
}, [hadChildren, hasChildren])
useEffect(() => {
setTitle(tag.title || '')
}, [setTitle, tag])
const toggleChildren: MouseEventHandler = useCallback(
(e) => {
e.stopPropagation()
setShowChildren((x) => {
tagsState.setExpanded(tag, !x)
return !x
})
},
[setShowChildren, tag, tagsState],
)
const selectCurrentTag = useCallback(async () => {
await tagsState.setSelectedTag(tag)
toggleAppPane(AppPaneId.Items)
}, [tagsState, tag, toggleAppPane])
const onBlur = useCallback(() => {
tagsState.save(tag, title).catch(console.error)
setTitle(tag.title)
}, [tagsState, tag, title, setTitle])
const onInput: FormEventHandler = useCallback(
(e) => {
const value = (e.target as HTMLInputElement).value
setTitle(value)
},
[setTitle],
)
const onKeyDown: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
inputRef.current?.blur()
e.preventDefault()
useEffect(() => {
if (!hadChildren && hasChildren) {
setShowChildren(true)
}
},
[inputRef],
)
setHadChildren(hasChildren)
}, [hadChildren, hasChildren])
useEffect(() => {
if (isEditing) {
inputRef.current?.focus()
}
}, [inputRef, isEditing])
useEffect(() => {
setTitle(tag.title || '')
}, [setTitle, tag])
const onSubtagInput = useCallback((e) => {
const value = (e.target as HTMLInputElement).value
setSubtagTitle(value)
}, [])
const onSubtagInputBlur = useCallback(() => {
tagsState.createSubtagAndAssignParent(tag, subtagTitle).catch(console.error)
setSubtagTitle('')
}, [subtagTitle, tag, tagsState])
const onSubtagKeyDown: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
e.preventDefault()
subtagInputRef.current?.blur()
}
},
[subtagInputRef],
)
useEffect(() => {
if (isAddingSubtag) {
subtagInputRef.current?.focus()
}
}, [subtagInputRef, isAddingSubtag])
const [, dragRef] = useDrag(
() => ({
type: ItemTypes.TAG,
item: { uuid: tag.uuid },
canDrag: () => {
return true
const toggleChildren: MouseEventHandler = useCallback(
(e) => {
e.stopPropagation()
setShowChildren((x) => {
tagsState.setExpanded(tag, !x)
return !x
})
},
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
}),
[tag],
)
[setShowChildren, tag, tagsState],
)
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
() => ({
accept: ItemTypes.TAG,
canDrop: (item) => {
return tagsState.isValidTagParent(tag, item as SNTag)
const selectCurrentTag = useCallback(async () => {
await tagsState.setSelectedTag(tag)
toggleAppPane(AppPaneId.Items)
}, [tagsState, tag, toggleAppPane])
const onBlur = useCallback(() => {
tagsState.save(tag, title).catch(console.error)
setTitle(tag.title)
}, [tagsState, tag, title, setTitle])
const onInput: FormEventHandler = useCallback(
(e) => {
const value = (e.target as HTMLInputElement).value
setTitle(value)
},
drop: (item) => {
if (!hasFolders) {
premiumModal.activate(TAG_FOLDERS_FEATURE_NAME)
return
}
tagsState.assignParent(item.uuid, tag.uuid).catch(console.error)
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
}),
}),
[tag, tagsState, hasFolders, premiumModal],
)
[setTitle],
)
const readyToDrop = isOver && canDrop
const toggleContextMenu = useCallback(() => {
if (!menuButtonRef.current) {
return
}
const contextMenuOpen = tagsState.contextMenuOpen
const menuButtonRect = menuButtonRef.current?.getBoundingClientRect()
if (contextMenuOpen) {
tagsState.setContextMenuOpen(false)
} else {
onContextMenu(tag, menuButtonRect.right, menuButtonRect.top)
}
}, [onContextMenu, tagsState, tag])
return (
<>
<button
className={`tag focus:shadow-inner ${isSelected ? 'selected' : ''} ${readyToDrop ? 'is-drag-over' : ''}`}
onClick={selectCurrentTag}
ref={dragRef}
style={{
paddingLeft: `${level * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`,
}}
onContextMenu={(e) => {
const onKeyDown: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
inputRef.current?.blur()
e.preventDefault()
onContextMenu(tag, e.clientX, e.clientY)
}}
>
<div className="tag-info" title={title} ref={dropRef}>
{hasAtLeastOneFolder && (
<div className="tag-fold-container">
<a
role="button"
className={`tag-fold focus:shadow-inner ${showChildren ? 'opened' : 'closed'} ${
!hasChildren ? 'invisible' : ''
}`}
onClick={hasChildren ? toggleChildren : undefined}
>
<Icon className={'text-neutral'} type={showChildren ? 'menu-arrow-down-alt' : 'menu-arrow-right'} />
</a>
</div>
)}
<div className={'tag-icon draggable mr-2'} ref={dragRef}>
<Icon type="hashtag" className={`${isSelected ? 'text-info' : 'text-neutral'}`} />
</div>
<input
className={`title focus:shadow-none focus:outline-none ${isEditing ? 'editing' : ''}`}
id={`react-tag-${tag.uuid}`}
disabled={!isEditing}
onBlur={onBlur}
onInput={onInput}
value={title}
onKeyDown={onKeyDown}
spellCheck={false}
ref={inputRef}
/>
<div className="flex items-center">
<a
role="button"
className={`mr-2 cursor-pointer border-0 bg-transparent hover:bg-contrast focus:shadow-inner ${
isSelected ? 'visible' : 'invisible'
}`}
onClick={toggleContextMenu}
ref={menuButtonRef}
>
<Icon type="more" className="text-neutral" />
</a>
<div className="count">{noteCounts.get()}</div>
</div>
</div>
}
},
[inputRef],
)
<div className={`meta ${hasAtLeastOneFolder ? 'with-folders' : ''}`}>
{tag.conflictOf && <div className="danger text-[0.625rem] font-bold">Conflicted Copy {tag.conflictOf}</div>}
</div>
</button>
{isAddingSubtag && (
<div
className="tag overflow-hidden"
useEffect(() => {
if (isEditing) {
inputRef.current?.focus()
}
}, [inputRef, isEditing])
const onSubtagInput = useCallback((e) => {
const value = (e.target as HTMLInputElement).value
setSubtagTitle(value)
}, [])
const onSubtagInputBlur = useCallback(() => {
tagsState.createSubtagAndAssignParent(tag, subtagTitle).catch(console.error)
setSubtagTitle('')
}, [subtagTitle, tag, tagsState])
const onSubtagKeyDown: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
e.preventDefault()
subtagInputRef.current?.blur()
}
},
[subtagInputRef],
)
useEffect(() => {
if (isAddingSubtag) {
subtagInputRef.current?.focus()
}
}, [subtagInputRef, isAddingSubtag])
const [, dragRef] = useDrag(
() => ({
type: ItemTypes.TAG,
item: { uuid: tag.uuid },
canDrag: () => {
return true
},
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
}),
[tag],
)
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
() => ({
accept: ItemTypes.TAG,
canDrop: (item) => {
return tagsState.isValidTagParent(tag, item as SNTag)
},
drop: (item) => {
if (!hasFolders) {
premiumModal.activate(TAG_FOLDERS_FEATURE_NAME)
return
}
tagsState.assignParent(item.uuid, tag.uuid).catch(console.error)
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
}),
}),
[tag, tagsState, hasFolders, premiumModal],
)
const readyToDrop = isOver && canDrop
const toggleContextMenu = useCallback(() => {
if (!menuButtonRef.current) {
return
}
const contextMenuOpen = tagsState.contextMenuOpen
const menuButtonRect = menuButtonRef.current?.getBoundingClientRect()
if (contextMenuOpen) {
tagsState.setContextMenuOpen(false)
} else {
onContextMenu(tag, menuButtonRect.right, menuButtonRect.top)
}
}, [onContextMenu, tagsState, tag])
return (
<>
<button
className={`tag focus:shadow-inner ${isSelected ? 'selected' : ''} ${readyToDrop ? 'is-drag-over' : ''}`}
onClick={selectCurrentTag}
ref={dragRef}
style={{
paddingLeft: `${(level + 1) * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`,
paddingLeft: `${
isCollapsed
? level * PADDING_PER_LEVEL_WHEN_COLLAPSED_PX + PADDING_BASE_WHEN_COLLAPSED_PX
: level * PADDING_PER_LEVEL_PX + PADDING_BASE_PX
}px`,
}}
onContextMenu={(e) => {
e.preventDefault()
onContextMenu(tag, e.clientX, e.clientY)
}}
>
<div className="tag-info">
<div className="flex h-full min-w-[22px] items-center border-0 bg-transparent p-0" />
<div className="tag-icon mr-1">
<Icon type="hashtag" className="mr-1 text-neutral" />
<div className="tag-info" title={title} ref={dropRef}>
{hasAtLeastOneFolder && (
<div className="tag-fold-container">
<a
role="button"
className={`tag-fold focus:shadow-inner ${showChildren ? 'opened' : 'closed'} ${
!hasChildren ? 'invisible' : ''
}`}
onClick={hasChildren ? toggleChildren : undefined}
>
<Icon className={'text-neutral'} type={showChildren ? 'menu-arrow-down-alt' : 'menu-arrow-right'} />
</a>
</div>
)}
<div
className={classNames('tag-icon draggable mr-2', isCollapsed ? 'md-only:!hidden lg-only:!hidden' : '')}
ref={dragRef}
>
<Icon type="hashtag" className={`${isSelected ? 'text-info' : 'text-neutral'}`} />
</div>
<input
className="title w-full focus:shadow-none focus:outline-none"
type="text"
ref={subtagInputRef}
onBlur={onSubtagInputBlur}
onKeyDown={onSubtagKeyDown}
value={subtagTitle}
onInput={onSubtagInput}
className={classNames(
'title focus:shadow-none focus:outline-none',
isEditing ? 'editing' : '',
isCollapsed ? 'md-only:!w-min lg-only:!w-min' : '',
)}
id={`react-tag-${tag.uuid}`}
disabled={!isEditing}
onBlur={onBlur}
onInput={onInput}
value={title}
onKeyDown={onKeyDown}
spellCheck={false}
ref={inputRef}
/>
<div className="flex items-center">
<a
role="button"
className={`mr-2 cursor-pointer border-0 bg-transparent hover:bg-contrast focus:shadow-inner ${
isSelected ? 'visible' : 'invisible'
}`}
onClick={toggleContextMenu}
ref={menuButtonRef}
>
<Icon type="more" className="text-neutral" />
</a>
<div className="count">{noteCounts.get()}</div>
</div>
</div>
</div>
)}
{showChildren && (
<>
{childrenTags.map((tag) => {
return (
<TagsListItem
level={level + 1}
key={tag.uuid}
tag={tag}
tagsState={tagsState}
features={features}
onContextMenu={onContextMenu}
<div className={`meta ${hasAtLeastOneFolder ? 'with-folders' : ''}`}>
{tag.conflictOf && <div className="danger text-[0.625rem] font-bold">Conflicted Copy {tag.conflictOf}</div>}
</div>
</button>
{isAddingSubtag && (
<div
className="tag overflow-hidden"
style={{
paddingLeft: `${(level + 1) * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`,
}}
>
<div className="tag-info">
<div className="flex h-full min-w-[22px] items-center border-0 bg-transparent p-0" />
<div className="tag-icon mr-1">
<Icon type="hashtag" className="mr-1 text-neutral" />
</div>
<input
className="title w-full focus:shadow-none focus:outline-none"
type="text"
ref={subtagInputRef}
onBlur={onSubtagInputBlur}
onKeyDown={onSubtagKeyDown}
value={subtagTitle}
onInput={onSubtagInput}
/>
)
})}
</>
)}
</>
)
})
</div>
</div>
)}
{showChildren && (
<>
{childrenTags.map((tag) => {
return (
<TagsListItem
level={level + 1}
key={tag.uuid}
tag={tag}
tagsState={tagsState}
features={features}
onContextMenu={onContextMenu}
isCollapsed={isCollapsed}
/>
)
})}
</>
)}
</>
)
},
)
TagsListItem.displayName = 'TagsListItem'
@@ -5,12 +5,14 @@ import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
import TagsSectionAddButton from './TagsSectionAddButton'
import TagsSectionTitle from './TagsSectionTitle'
import { classNames } from '@/Utils/ConcatenateClassNames'
type Props = {
viewControllerManager: ViewControllerManager
isCollapsed: boolean
}
const TagsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
const TagsSection: FunctionComponent<Props> = ({ viewControllerManager, isCollapsed }) => {
const [hasMigration, setHasMigration] = useState<boolean>(false)
const checkIfMigrationNeeded = useCallback(() => {
@@ -53,7 +55,7 @@ const TagsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
return (
<section>
<div className="section-title-bar">
<div className={classNames('section-title-bar', isCollapsed ? 'md-only:hidden lg-only:hidden' : '')}>
<div className="section-title-bar-header">
<TagsSectionTitle
features={viewControllerManager.featuresController}
@@ -66,7 +68,13 @@ const TagsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
/>
</div>
</div>
<TagsList viewControllerManager={viewControllerManager} />
<div
className={classNames(
'hidden',
isCollapsed ? 'mt-6 mb-7 border border-border md-only:block lg-only:block' : '',
)}
/>
<TagsList viewControllerManager={viewControllerManager} isCollapsed={isCollapsed} />
</section>
)
}
@@ -7,15 +7,16 @@ import { FunctionComponent } from 'react'
type Props = {
tags: NavigationController
features: FeaturesController
className?: string
}
const TagsSectionAddButton: FunctionComponent<Props> = ({ tags }) => {
const TagsSectionAddButton: FunctionComponent<Props> = ({ tags, className = '' }) => {
return (
<IconButton
focusable={true}
icon="add"
title="Create a new tag"
className="p-0 text-neutral"
className={`p-0 text-neutral ${className}`}
onClick={() => tags.createNewTemplate()}
/>
)
@@ -9,9 +9,10 @@ type Props = {
features: FeaturesController
hasMigration: boolean
onClickMigration: () => void
className?: string
}
const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, onClickMigration }) => {
const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, onClickMigration, className = '' }) => {
const entitledToFolders = features.hasFolders
const modal = usePremiumModal()
@@ -22,7 +23,7 @@ const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, on
if (entitledToFolders) {
return (
<>
<div className="title text-sm">
<div className={`title text-sm ${className}`}>
<span className="font-bold">Folders</span>
{hasMigration && (
<label className="ml-1 cursor-pointer font-bold text-info" onClick={onClickMigration}>
@@ -36,7 +37,7 @@ const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, on
return (
<>
<div className="title text-sm">
<div className={`title text-sm ${className}`}>
<span className="font-bold">Tags</span>
<Tooltip label={TAG_FOLDERS_FEATURE_TOOLTIP}>
<label className="ml-1 cursor-pointer font-bold text-passive-2" onClick={showPremiumAlert}>
@@ -9,22 +9,6 @@
}
}
.app-column-first {
width: 220px;
@media screen and (max-width: 768px) {
width: 100% !important;
}
}
.app-column-second {
width: 350px;
@media screen and (max-width: 768px) {
width: 100% !important;
}
}
.app-column {
overflow: hidden;
+4
View File
@@ -108,8 +108,12 @@ p {
}
.main-ui-view {
// Fallbacks
min-height: 100vh;
height: 100vh;
// Mobile-corrected viewport height
min-height: var(--viewport-height);
height: var(--viewport-height);
position: relative;
overflow: auto;
background-color: var(--editor-header-bar-background-color);
+15
View File
@@ -132,6 +132,21 @@
@include DimmedBackground(var(--sn-stylekit-info-color), 0.08);
}
.transparent-info-color-background {
&::after {
@include DimmedBackground(var(--sn-stylekit-info-color), .12);
border-radius: 100px;
}
}
.transparent-info-color-background-hover {
&:hover {
&::after {
@extend .transparent-info-color-background
}
}
}
svg.sk-circular-progress {
$pi: 3.14159265358979;
+11
View File
@@ -1,4 +1,5 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/javascripts/**/*.tsx', '../toast/src/**/*.tsx'],
theme: {
@@ -9,6 +10,7 @@ module.exports = {
26: '6.5rem',
30: '7.5rem',
70: '17.5rem',
87.5: '21.875rem',
125: '31.25rem',
160: '40rem',
},
@@ -70,6 +72,15 @@ module.exports = {
fontSize: {
'menu-item': '0.813rem',
},
screens: {
'xsm-only': { min: '320px', max: '639px' },
'sm-only': { min: '640px', max: '767px' },
'md-only': { min: '768px', max: '1023px' },
'lg-only': { min: '1024px', max: '1279px' },
},
transitionProperty: {
width: 'width',
},
},
colors: {
transparent: 'transparent',
+1 -10
View File
@@ -12,16 +12,7 @@ module.exports = (env, argv) => {
optimization: {
minimize: false,
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
inject: true,
templateParameters: {
env: process.env,
},
}),
new ReactRefreshWebpackPlugin(),
],
plugins: [new ReactRefreshWebpackPlugin()],
devServer: {
hot: true,
static: './dist',