mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44c5c1c27a | |||
| 33aa956b20 | |||
| 9f6f806912 | |||
| 0208e6e42f | |||
| f088375009 | |||
| e8db938655 |
@@ -9,6 +9,7 @@ import DocumentMeta from "~/components/DocumentMeta";
|
||||
import DocumentViews from "~/components/DocumentViews";
|
||||
import Popover from "~/components/Popover";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import Fade from "./Fade";
|
||||
|
||||
type Props = {
|
||||
document: Document;
|
||||
@@ -23,6 +24,7 @@ function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
|
||||
const documentViews = useObserver(() => views.inDocument(document.id));
|
||||
const totalViewers = documentViews.length;
|
||||
const onlyYou = totalViewers === 1 && documentViews[0].user.id;
|
||||
const viewsLoadedOnMount = React.useRef(totalViewers > 0);
|
||||
|
||||
const popover = usePopoverState({
|
||||
gutter: 8,
|
||||
@@ -30,12 +32,14 @@ function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
|
||||
modal: true,
|
||||
});
|
||||
|
||||
const Wrapper = viewsLoadedOnMount.current ? React.Fragment : Fade;
|
||||
|
||||
return (
|
||||
<Meta document={document} to={to} replace {...rest}>
|
||||
{totalViewers && !isDraft ? (
|
||||
<PopoverDisclosure {...popover}>
|
||||
{(props) => (
|
||||
<>
|
||||
<Wrapper>
|
||||
•
|
||||
<a {...props}>
|
||||
{t("Viewed by")}{" "}
|
||||
@@ -45,7 +49,7 @@ function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
|
||||
totalViewers === 1 ? t("person") : t("people")
|
||||
}`}
|
||||
</a>
|
||||
</>
|
||||
</Wrapper>
|
||||
)}
|
||||
</PopoverDisclosure>
|
||||
) : null}
|
||||
|
||||
@@ -54,11 +54,14 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
|
||||
@observable
|
||||
isFetching = false;
|
||||
|
||||
@observable
|
||||
isFetchingInitial = !this.props.items?.length;
|
||||
|
||||
@observable
|
||||
fetchCounter = 0;
|
||||
|
||||
@observable
|
||||
renderCount: number = DEFAULT_PAGINATION_LIMIT;
|
||||
renderCount = 15;
|
||||
|
||||
@observable
|
||||
offset = 0;
|
||||
@@ -85,6 +88,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
|
||||
this.allowLoadMore = true;
|
||||
this.renderCount = DEFAULT_PAGINATION_LIMIT;
|
||||
this.isFetching = false;
|
||||
this.isFetchingInitial = false;
|
||||
this.isFetchingMore = false;
|
||||
};
|
||||
|
||||
@@ -112,6 +116,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
|
||||
}
|
||||
|
||||
this.renderCount += limit;
|
||||
this.isFetchingInitial = false;
|
||||
} catch (err) {
|
||||
this.error = err;
|
||||
} finally {
|
||||
@@ -159,7 +164,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
|
||||
const showLoading =
|
||||
this.isFetching &&
|
||||
!this.isFetchingMore &&
|
||||
(!items?.length || this.fetchCounter === 0);
|
||||
(!items?.length || (this.fetchCounter <= 1 && this.isFetchingInitial));
|
||||
|
||||
if (showLoading) {
|
||||
return (
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "react-router";
|
||||
import { Link } from "react-router-dom";
|
||||
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||
import history from "~/utils/history";
|
||||
|
||||
const resolveToLocation = (
|
||||
to: LocationDescriptor | ((location: Location) => LocationDescriptor),
|
||||
@@ -35,14 +36,16 @@ export type Props = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
||||
activeStyle?: React.CSSProperties;
|
||||
scrollIntoViewIfNeeded?: boolean;
|
||||
exact?: boolean;
|
||||
replace?: boolean;
|
||||
isActive?: (match: match | null, location: Location) => boolean;
|
||||
location?: Location;
|
||||
strict?: boolean;
|
||||
to: LocationDescriptor;
|
||||
onBeforeClick?: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* A <Link> wrapper that knows if it's "active" or not.
|
||||
* A <Link> wrapper that clicks extra fast and knows if it's "active" or not.
|
||||
*/
|
||||
const NavLink = ({
|
||||
"aria-current": ariaCurrent = "page",
|
||||
@@ -53,13 +56,19 @@ const NavLink = ({
|
||||
isActive: isActiveProp,
|
||||
location: locationProp,
|
||||
strict,
|
||||
replace,
|
||||
style: styleProp,
|
||||
scrollIntoViewIfNeeded,
|
||||
onClick,
|
||||
onBeforeClick,
|
||||
to,
|
||||
...rest
|
||||
}: Props) => {
|
||||
const linkRef = React.useRef(null);
|
||||
const context = React.useContext(RouterContext);
|
||||
const [preActive, setPreActive] = React.useState<boolean | undefined>(
|
||||
undefined
|
||||
);
|
||||
const currentLocation = locationProp || context.location;
|
||||
const toLocation = normalizeToLocation(
|
||||
resolveToLocation(to, currentLocation),
|
||||
@@ -67,25 +76,24 @@ const NavLink = ({
|
||||
);
|
||||
const { pathname: path } = toLocation;
|
||||
|
||||
// Regex taken from: https://github.com/pillarjs/path-to-regexp/blob/master/index.js#L202
|
||||
const escapedPath = path?.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
|
||||
const match = escapedPath
|
||||
const match = path
|
||||
? matchPath(currentLocation.pathname, {
|
||||
path: escapedPath,
|
||||
// Regex taken from: https://github.com/pillarjs/path-to-regexp/blob/master/index.js#L202
|
||||
path: path.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"),
|
||||
exact,
|
||||
strict,
|
||||
})
|
||||
: null;
|
||||
|
||||
const isActive = !!(isActiveProp
|
||||
? isActiveProp(match, currentLocation)
|
||||
: match);
|
||||
const isActive =
|
||||
preActive ??
|
||||
!!(isActiveProp ? isActiveProp(match, currentLocation) : match);
|
||||
const className = isActive
|
||||
? joinClassnames(classNameProp, activeClassName)
|
||||
: classNameProp;
|
||||
const style = isActive ? { ...styleProp, ...activeStyle } : styleProp;
|
||||
|
||||
React.useEffect(() => {
|
||||
React.useLayoutEffect(() => {
|
||||
if (isActive && linkRef.current && scrollIntoViewIfNeeded !== false) {
|
||||
scrollIntoView(linkRef.current, {
|
||||
scrollMode: "if-needed",
|
||||
@@ -94,15 +102,73 @@ const NavLink = ({
|
||||
}
|
||||
}, [linkRef, scrollIntoViewIfNeeded, isActive]);
|
||||
|
||||
const props = {
|
||||
"aria-current": (isActive && ariaCurrent) || undefined,
|
||||
className,
|
||||
style,
|
||||
to: toLocation,
|
||||
...rest,
|
||||
};
|
||||
const shouldFastClick = React.useCallback(
|
||||
(event: React.MouseEvent<HTMLAnchorElement>): boolean => {
|
||||
return (
|
||||
event.button === 0 && // Only intercept left clicks
|
||||
!event.defaultPrevented &&
|
||||
!rest.target &&
|
||||
!event.altKey &&
|
||||
!event.metaKey &&
|
||||
!event.ctrlKey
|
||||
);
|
||||
},
|
||||
[rest.target]
|
||||
);
|
||||
|
||||
return <Link ref={linkRef} {...props} />;
|
||||
const navigateTo = React.useCallback(() => {
|
||||
if (replace) {
|
||||
history.replace(to);
|
||||
} else {
|
||||
history.push(to);
|
||||
}
|
||||
}, [to, replace]);
|
||||
|
||||
const handleClick = React.useCallback(
|
||||
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
onClick?.(event);
|
||||
|
||||
if (shouldFastClick(event)) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
event.currentTarget.focus();
|
||||
|
||||
setPreActive(true);
|
||||
|
||||
// Wait a frame until following the link
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(navigateTo);
|
||||
event.currentTarget?.blur();
|
||||
});
|
||||
}
|
||||
},
|
||||
[onClick, navigateTo, shouldFastClick]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
setPreActive(undefined);
|
||||
}, [currentLocation]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={isActive ? "active" : "inactive"}
|
||||
ref={linkRef}
|
||||
//onMouseDown={handleClick}
|
||||
onKeyDown={(event) => {
|
||||
if (["Enter", " "].includes(event.key)) {
|
||||
navigateTo();
|
||||
event.currentTarget?.blur();
|
||||
}
|
||||
}}
|
||||
onClick={handleClick}
|
||||
aria-current={(isActive && ariaCurrent) || undefined}
|
||||
className={className}
|
||||
style={style}
|
||||
to={toLocation}
|
||||
replace={replace}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavLink;
|
||||
|
||||
@@ -14,6 +14,7 @@ const TabLink = styled(NavLink)`
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
cursor: var(--pointer);
|
||||
color: ${(props) => props.theme.textTertiary};
|
||||
margin-right: 24px;
|
||||
padding: 6px 0;
|
||||
|
||||
@@ -23,8 +23,6 @@ function EmptyCollection({ collection }: Props) {
|
||||
const context = useActionContext();
|
||||
const collectionName = collection ? collection.name : "";
|
||||
|
||||
console.log({ context });
|
||||
|
||||
return (
|
||||
<Centered column>
|
||||
<Text type="secondary">
|
||||
|
||||
Reference in New Issue
Block a user