Commit b144faf4 authored by Steven's avatar Steven

feat: add location selector

parent 63989ab3
...@@ -215,6 +215,8 @@ message CreateMemoRequest { ...@@ -215,6 +215,8 @@ message CreateMemoRequest {
repeated Resource resources = 3; repeated Resource resources = 3;
repeated MemoRelation relations = 4; repeated MemoRelation relations = 4;
optional Location location = 5;
} }
message ListMemosRequest { message ListMemosRequest {
......
This diff is collapsed.
...@@ -2384,6 +2384,8 @@ definitions: ...@@ -2384,6 +2384,8 @@ definitions:
items: items:
type: object type: object
$ref: '#/definitions/v1MemoRelation' $ref: '#/definitions/v1MemoRelation'
location:
$ref: '#/definitions/apiv1Location'
v1CreateWebhookRequest: v1CreateWebhookRequest:
type: object type: object
properties: properties:
......
...@@ -66,6 +66,9 @@ func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoR ...@@ -66,6 +66,9 @@ func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoR
create.Payload = &storepb.MemoPayload{ create.Payload = &storepb.MemoPayload{
Property: property, Property: property,
} }
if request.Location != nil {
create.Payload.Location = convertLocationToStore(request.Location)
}
memo, err := s.Store.CreateMemo(ctx, create) memo, err := s.Store.CreateMemo(ctx, create)
if err != nil { if err != nil {
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
"highlight.js": "^11.10.0", "highlight.js": "^11.10.0",
"i18next": "^23.15.1", "i18next": "^23.15.1",
"katex": "^0.16.11", "katex": "^0.16.11",
"leaflet": "^1.9.4",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lucide-react": "^0.437.0", "lucide-react": "^0.437.0",
"mermaid": "^10.9.1", "mermaid": "^10.9.1",
...@@ -35,6 +36,7 @@ ...@@ -35,6 +36,7 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-i18next": "^15.0.2", "react-i18next": "^15.0.2",
"react-leaflet": "^4.2.1",
"react-redux": "^9.1.2", "react-redux": "^9.1.2",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
"react-use": "^17.5.1", "react-use": "^17.5.1",
...@@ -52,6 +54,7 @@ ...@@ -52,6 +54,7 @@
"@types/d3": "^7.4.3", "@types/d3": "^7.4.3",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@types/katex": "^0.16.7", "@types/katex": "^0.16.7",
"@types/leaflet": "^1.9.12",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^22.5.5", "@types/node": "^22.5.5",
"@types/qs": "^6.9.16", "@types/qs": "^6.9.16",
......
...@@ -65,6 +65,9 @@ importers: ...@@ -65,6 +65,9 @@ importers:
katex: katex:
specifier: ^0.16.11 specifier: ^0.16.11
version: 0.16.11 version: 0.16.11
leaflet:
specifier: ^1.9.4
version: 1.9.4
lodash-es: lodash-es:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
...@@ -86,6 +89,9 @@ importers: ...@@ -86,6 +89,9 @@ importers:
react-i18next: react-i18next:
specifier: ^15.0.2 specifier: ^15.0.2
version: 15.0.2(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 15.0.2(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-leaflet:
specifier: ^4.2.1
version: 4.2.1(leaflet@1.9.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-redux: react-redux:
specifier: ^9.1.2 specifier: ^9.1.2
version: 9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1) version: 9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1)
...@@ -132,6 +138,9 @@ importers: ...@@ -132,6 +138,9 @@ importers:
'@types/katex': '@types/katex':
specifier: ^0.16.7 specifier: ^0.16.7
version: 0.16.7 version: 0.16.7
'@types/leaflet':
specifier: ^1.9.12
version: 1.9.12
'@types/lodash-es': '@types/lodash-es':
specifier: ^4.17.12 specifier: ^4.17.12
version: 4.17.12 version: 4.17.12
...@@ -1020,6 +1029,13 @@ packages: ...@@ -1020,6 +1029,13 @@ packages:
'@radix-ui/rect@1.1.0': '@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
'@react-leaflet/core@2.1.0':
resolution: {integrity: sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==}
peerDependencies:
leaflet: ^1.9.0
react: ^18.0.0
react-dom: ^18.0.0
'@reduxjs/toolkit@2.2.7': '@reduxjs/toolkit@2.2.7':
resolution: {integrity: sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==} resolution: {integrity: sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==}
peerDependencies: peerDependencies:
...@@ -1247,6 +1263,9 @@ packages: ...@@ -1247,6 +1263,9 @@ packages:
'@types/katex@0.16.7': '@types/katex@0.16.7':
resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
'@types/leaflet@1.9.12':
resolution: {integrity: sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==}
'@types/lodash-es@4.17.12': '@types/lodash-es@4.17.12':
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
...@@ -2377,6 +2396,9 @@ packages: ...@@ -2377,6 +2396,9 @@ packages:
layout-base@1.0.2: layout-base@1.0.2:
resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
leaflet@1.9.4:
resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==}
less@4.2.0: less@4.2.0:
resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==} resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==}
engines: {node: '>=6'} engines: {node: '>=6'}
...@@ -2790,6 +2812,13 @@ packages: ...@@ -2790,6 +2812,13 @@ packages:
react-is@18.3.1: react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
react-leaflet@4.2.1:
resolution: {integrity: sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==}
peerDependencies:
leaflet: ^1.9.0
react: ^18.0.0
react-dom: ^18.0.0
react-redux@9.1.2: react-redux@9.1.2:
resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==} resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==}
peerDependencies: peerDependencies:
...@@ -4144,6 +4173,12 @@ snapshots: ...@@ -4144,6 +4173,12 @@ snapshots:
'@radix-ui/rect@1.1.0': {} '@radix-ui/rect@1.1.0': {}
'@react-leaflet/core@2.1.0(leaflet@1.9.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
leaflet: 1.9.4
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@reduxjs/toolkit@2.2.7(react-redux@9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': '@reduxjs/toolkit@2.2.7(react-redux@9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1))(react@18.3.1)':
dependencies: dependencies:
immer: 10.1.1 immer: 10.1.1
...@@ -4370,6 +4405,10 @@ snapshots: ...@@ -4370,6 +4405,10 @@ snapshots:
'@types/katex@0.16.7': {} '@types/katex@0.16.7': {}
'@types/leaflet@1.9.12':
dependencies:
'@types/geojson': 7946.0.14
'@types/lodash-es@4.17.12': '@types/lodash-es@4.17.12':
dependencies: dependencies:
'@types/lodash': 4.17.7 '@types/lodash': 4.17.7
...@@ -5667,6 +5706,8 @@ snapshots: ...@@ -5667,6 +5706,8 @@ snapshots:
layout-base@1.0.2: {} layout-base@1.0.2: {}
leaflet@1.9.4: {}
less@4.2.0: less@4.2.0:
dependencies: dependencies:
copy-anything: 2.0.6 copy-anything: 2.0.6
...@@ -6177,6 +6218,13 @@ snapshots: ...@@ -6177,6 +6218,13 @@ snapshots:
react-is@18.3.1: {} react-is@18.3.1: {}
react-leaflet@4.2.1(leaflet@1.9.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@react-leaflet/core': 2.1.0(leaflet@1.9.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
leaflet: 1.9.4
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-redux@9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1): react-redux@9.1.2(@types/react@18.3.8)(react@18.3.1)(redux@5.0.1):
dependencies: dependencies:
'@types/use-sync-external-store': 0.0.3 '@types/use-sync-external-store': 0.0.3
......
import { LatLng } from "leaflet";
import { useEffect, useState } from "react";
import { MapContainer, Marker, TileLayer, useMapEvents } from "react-leaflet";
interface MarkerProps {
position: LatLng | undefined;
onChange: (position: LatLng) => void;
}
const LocationMarker = (props: MarkerProps) => {
const [position, setPosition] = useState(props.position);
const map = useMapEvents({
click(e) {
setPosition(e.latlng);
map.locate();
// Call the parent onChange function.
props.onChange(e.latlng);
},
locationfound() {},
});
useEffect(() => {
map.attributionControl.setPrefix("");
map.locate();
}, []);
return position === undefined ? null : <Marker position={position}></Marker>;
};
interface MapProps {
latlng?: LatLng;
onChange?: (position: LatLng) => void;
}
const LeafletMap = (props: MapProps) => {
return (
<MapContainer className="w-full h-72" center={props.latlng} zoom={13} scrollWheelZoom={false}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution="" />
<LocationMarker position={props.latlng} onChange={props.onChange ? props.onChange : () => {}} />
</MapContainer>
);
};
export default LeafletMap;
...@@ -151,7 +151,7 @@ const AddMemoRelationPopover = (props: Props) => { ...@@ -151,7 +151,7 @@ const AddMemoRelationPopover = (props: Props) => {
getOptionLabel={(memo) => memo.content} getOptionLabel={(memo) => memo.content}
isOptionEqualToValue={(memo, value) => memo.name === value.name} isOptionEqualToValue={(memo, value) => memo.name === value.name}
renderOption={(props, memo) => ( renderOption={(props, memo) => (
<AutocompleteOption {...props}> <AutocompleteOption {...props} key={memo.name}>
<div className="w-full flex flex-col justify-start items-start"> <div className="w-full flex flex-col justify-start items-start">
<p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p> <p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p>
<p className="mt-0.5 text-sm leading-5 line-clamp-2">{searchText ? getHighlightedContent(memo.content) : memo.snippet}</p> <p className="mt-0.5 text-sm leading-5 line-clamp-2">{searchText ? getHighlightedContent(memo.content) : memo.snippet}</p>
......
import { Button, IconButton, Input } from "@mui/joy";
import { LatLng } from "leaflet";
import { MapPinIcon } from "lucide-react";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import LeafletMap from "@/components/LeafletMap";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover";
import { Location } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n";
interface Props {
location?: Location;
onChange: (location: Location) => void;
}
interface State {
placeholder: string;
position: LatLng;
}
const LocationSelector = (props: Props) => {
const t = useTranslate();
const [state, setState] = useState<State>({
placeholder: props.location?.placeholder || "",
position: new LatLng(props.location?.latitude || 0, props.location?.longitude || 0),
});
const [popoverOpen, setPopoverOpen] = useState<boolean>(false);
useEffect(() => {
if (popoverOpen && !props.location) {
const handleError = (error: any, errorMessage: string) => {
toast.error(errorMessage);
console.error(error);
};
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
setState({ ...state, position: new LatLng(lat, lng) });
},
(error) => {
handleError(error, "Error getting current position");
},
);
} else {
handleError("Geolocation is not supported by this browser.", "Geolocation is not supported by this browser.");
}
}
}, [popoverOpen]);
useEffect(() => {
// Fetch reverse geocoding data.
fetch(`https://nominatim.openstreetmap.org/reverse?lat=${state.position.lat}&lon=${state.position.lng}&format=json`)
.then((response) => response.json())
.then((data) => {
if (data && data.display_name) {
setState({ ...state, placeholder: data.display_name });
}
})
.catch((error) => {
toast.error("Failed to fetch reverse geocoding data");
console.error("Failed to fetch reverse geocoding data:", error);
});
}, [state.position]);
const onPositionChanged = (position: LatLng) => {
setState({ ...state, position });
};
return (
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
<PopoverTrigger>
<IconButton size="sm" component="div">
<MapPinIcon className="w-5 h-5 mx-auto shrink-0" />
{props.location && (
<span className="font-normal ml-0.5 text-ellipsis whitespace-nowrap overflow-hidden max-w-32">
{props.location.placeholder}
</span>
)}
</IconButton>
</PopoverTrigger>
<PopoverContent align="center">
<div className="min-w-80 sm:w-128 flex flex-col justify-start items-start">
<LeafletMap key={JSON.stringify(state.position)} latlng={state.position} onChange={onPositionChanged} />
<div className="mt-2 w-full flex flex-row justify-between items-center gap-2">
<Input
placeholder="Choose location"
value={state.placeholder}
onChange={(e) => setState((state) => ({ ...state, placeholder: e.target.value }))}
/>
<Button
size="sm"
onClick={() => {
props.onChange(
Location.fromPartial({
placeholder: state.placeholder,
latitude: state.position.lat,
longitude: state.position.lng,
}),
);
setPopoverOpen(false);
}}
disabled={!state.position || state.placeholder.length === 0}
>
{t("common.add")}
</Button>
</div>
</div>
</PopoverContent>
</Popover>
);
};
export default LocationSelector;
...@@ -12,7 +12,7 @@ import useAsyncEffect from "@/hooks/useAsyncEffect"; ...@@ -12,7 +12,7 @@ import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { useMemoStore, useResourceStore, useUserStore, useWorkspaceSettingStore } from "@/store/v1"; import { useMemoStore, useResourceStore, useUserStore, useWorkspaceSettingStore } from "@/store/v1";
import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service"; import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
import { Memo, Visibility } from "@/types/proto/api/v1/memo_service"; import { Location, Memo, Visibility } from "@/types/proto/api/v1/memo_service";
import { Resource } from "@/types/proto/api/v1/resource_service"; import { Resource } from "@/types/proto/api/v1/resource_service";
import { UserSetting } from "@/types/proto/api/v1/user_service"; import { UserSetting } from "@/types/proto/api/v1/user_service";
import { WorkspaceMemoRelatedSetting } from "@/types/proto/api/v1/workspace_setting_service"; import { WorkspaceMemoRelatedSetting } from "@/types/proto/api/v1/workspace_setting_service";
...@@ -21,6 +21,7 @@ import { useTranslate } from "@/utils/i18n"; ...@@ -21,6 +21,7 @@ import { useTranslate } from "@/utils/i18n";
import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo"; import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo";
import VisibilityIcon from "../VisibilityIcon"; import VisibilityIcon from "../VisibilityIcon";
import AddMemoRelationPopover from "./ActionButton/AddMemoRelationPopover"; import AddMemoRelationPopover from "./ActionButton/AddMemoRelationPopover";
import LocationSelector from "./ActionButton/LocationSelector";
import MarkdownMenu from "./ActionButton/MarkdownMenu"; import MarkdownMenu from "./ActionButton/MarkdownMenu";
import TagSelector from "./ActionButton/TagSelector"; import TagSelector from "./ActionButton/TagSelector";
import UploadResourceButton from "./ActionButton/UploadResourceButton"; import UploadResourceButton from "./ActionButton/UploadResourceButton";
...@@ -44,9 +45,11 @@ export interface Props { ...@@ -44,9 +45,11 @@ export interface Props {
} }
interface State { interface State {
initialized: boolean;
memoVisibility: Visibility; memoVisibility: Visibility;
resourceList: Resource[]; resourceList: Resource[];
relationList: MemoRelation[]; relationList: MemoRelation[];
location: Location | undefined;
isUploadingResource: boolean; isUploadingResource: boolean;
isRequesting: boolean; isRequesting: boolean;
isComposing: boolean; isComposing: boolean;
...@@ -62,9 +65,11 @@ const MemoEditor = (props: Props) => { ...@@ -62,9 +65,11 @@ const MemoEditor = (props: Props) => {
const resourceStore = useResourceStore(); const resourceStore = useResourceStore();
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
initialized: false,
memoVisibility: Visibility.PRIVATE, memoVisibility: Visibility.PRIVATE,
resourceList: [], resourceList: [],
relationList: [], relationList: [],
location: undefined,
isUploadingResource: false, isUploadingResource: false,
isRequesting: false, isRequesting: false,
isComposing: false, isComposing: false,
...@@ -107,6 +112,10 @@ const MemoEditor = (props: Props) => { ...@@ -107,6 +112,10 @@ const MemoEditor = (props: Props) => {
useAsyncEffect(async () => { useAsyncEffect(async () => {
if (!memoName) { if (!memoName) {
setState((prevState) => ({
...prevState,
initialized: true,
}));
return; return;
} }
...@@ -118,11 +127,16 @@ const MemoEditor = (props: Props) => { ...@@ -118,11 +127,16 @@ const MemoEditor = (props: Props) => {
memoVisibility: memo.visibility, memoVisibility: memo.visibility,
resourceList: memo.resources, resourceList: memo.resources,
relationList: memo.relations, relationList: memo.relations,
location: memo.location,
})); }));
setDisplayTime(memo.displayTime); setDisplayTime(memo.displayTime);
if (!contentCache) { if (!contentCache) {
editorRef.current?.setContent(memo.content ?? ""); editorRef.current?.setContent(memo.content ?? "");
} }
setState((prevState) => ({
...prevState,
initialized: true,
}));
} }
}, [memoName]); }, [memoName]);
...@@ -311,6 +325,10 @@ const MemoEditor = (props: Props) => { ...@@ -311,6 +325,10 @@ const MemoEditor = (props: Props) => {
updateMask.push("relations"); updateMask.push("relations");
memoPatch.relations = state.relationList; memoPatch.relations = state.relationList;
} }
if (!isEqual(state.location, prevMemo.location)) {
updateMask.push("location");
memoPatch.location = state.location;
}
const memo = await memoStore.updateMemo(memoPatch, updateMask); const memo = await memoStore.updateMemo(memoPatch, updateMask);
if (onConfirm) { if (onConfirm) {
onConfirm(memo.name); onConfirm(memo.name);
...@@ -324,6 +342,7 @@ const MemoEditor = (props: Props) => { ...@@ -324,6 +342,7 @@ const MemoEditor = (props: Props) => {
visibility: state.memoVisibility, visibility: state.memoVisibility,
resources: state.resourceList, resources: state.resourceList,
relations: state.relationList, relations: state.relationList,
location: state.location,
}) })
: memoServiceClient : memoServiceClient
.createMemoComment({ .createMemoComment({
...@@ -333,6 +352,7 @@ const MemoEditor = (props: Props) => { ...@@ -333,6 +352,7 @@ const MemoEditor = (props: Props) => {
visibility: state.memoVisibility, visibility: state.memoVisibility,
resources: state.resourceList, resources: state.resourceList,
relations: state.relationList, relations: state.relationList,
location: state.location,
}, },
}) })
.then((memo) => memo); .then((memo) => memo);
...@@ -383,6 +403,10 @@ const MemoEditor = (props: Props) => { ...@@ -383,6 +403,10 @@ const MemoEditor = (props: Props) => {
const allowSave = (hasContent || state.resourceList.length > 0) && !state.isUploadingResource && !state.isRequesting; const allowSave = (hasContent || state.resourceList.length > 0) && !state.isUploadingResource && !state.isRequesting;
if (!state.initialized) {
return null;
}
return ( return (
<MemoEditorContext.Provider <MemoEditorContext.Provider
value={{ value={{
...@@ -435,6 +459,15 @@ const MemoEditor = (props: Props) => { ...@@ -435,6 +459,15 @@ const MemoEditor = (props: Props) => {
<MarkdownMenu editorRef={editorRef} /> <MarkdownMenu editorRef={editorRef} />
<UploadResourceButton /> <UploadResourceButton />
<AddMemoRelationPopover editorRef={editorRef} /> <AddMemoRelationPopover editorRef={editorRef} />
<LocationSelector
location={state.location}
onChange={(location) =>
setState((prevState) => ({
...prevState,
location,
}))
}
/>
</div> </div>
</div> </div>
<Divider className="!mt-2 opacity-40" /> <Divider className="!mt-2 opacity-40" />
......
import { Tooltip } from "@mui/joy"; import { Tooltip } from "@mui/joy";
import clsx from "clsx"; import clsx from "clsx";
import { BookmarkIcon, MessageCircleMoreIcon } from "lucide-react"; import { BookmarkIcon, MapPinIcon, MessageCircleMoreIcon } from "lucide-react";
import { memo, useCallback, useEffect, useRef, useState } from "react"; import { memo, useCallback, useEffect, useRef, useState } from "react";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
...@@ -198,6 +198,14 @@ const MemoView: React.FC<Props> = (props: Props) => { ...@@ -198,6 +198,14 @@ const MemoView: React.FC<Props> = (props: Props) => {
onDoubleClick={handleMemoContentDoubleClick} onDoubleClick={handleMemoContentDoubleClick}
compact={props.compact && workspaceMemoRelatedSetting.enableAutoCompact} compact={props.compact && workspaceMemoRelatedSetting.enableAutoCompact}
/> />
{memo.location && (
<p className="w-full flex flex-row gap-0.5 items-center text-gray-500">
<MapPinIcon className="w-4 h-auto shrink-0" />
<span className="text-sm font-normal text-ellipsis whitespace-nowrap overflow-hidden">
{memo.location.placeholder ? memo.location.placeholder : `[${memo.location.latitude}, ${memo.location.longitude}]`}
</span>
</p>
)}
<MemoResourceListView resources={memo.resources} /> <MemoResourceListView resources={memo.resources} />
<MemoRelationListView memo={memo} relations={referencedMemos} /> <MemoRelationListView memo={memo} relations={referencedMemos} />
<MemoReactionistView memo={memo} reactions={memo.reactions} /> <MemoReactionistView memo={memo} reactions={memo.reactions} />
......
import "@github/relative-time-element"; import "@github/relative-time-element";
import { CssVarsProvider } from "@mui/joy"; import { CssVarsProvider } from "@mui/joy";
import "leaflet/dist/leaflet.css";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment