Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ web/dist
# Build artifacts
build/
bin/
memos


# Plan/design documents
docs/plans/
Expand Down
1 change: 1 addition & 0 deletions cmd/memos/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func printGreetings(profile *profile.Profile) {
}

func main() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})))
if err := rootCmd.Execute(); err != nil {
panic(err)
}
Expand Down
9 changes: 9 additions & 0 deletions server/router/api/v1/memo_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoR
Content: request.Memo.Content,
Visibility: convertVisibilityToStore(request.Memo.Visibility),
}
if request.Memo.CreateTime != nil {
create.CreatedTs = request.Memo.CreateTime.AsTime().Unix()
}
// If UpdateTime is provided, use it. Otherwise, if CreateTime is provided, use it for UpdatedTs as well.
if request.Memo.UpdateTime != nil {
create.UpdatedTs = request.Memo.UpdateTime.AsTime().Unix()
} else if request.Memo.CreateTime != nil {
create.UpdatedTs = request.Memo.CreateTime.AsTime().Unix()
}
instanceMemoRelatedSetting, err := s.Store.GetInstanceMemoRelatedSetting(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get instance memo related setting")
Expand Down
19 changes: 14 additions & 5 deletions store/db/postgres/memo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ import (
)

func (d *DB) CreateMemo(ctx context.Context, create *store.Memo) (*store.Memo, error) {
fields := []string{"uid", "creator_id", "content", "visibility", "payload"}
fields := []string{"uid", "creator_id", "content", "visibility"}
args := []any{create.UID, create.CreatorID, create.Content, create.Visibility}
if create.CreatedTs != 0 {
fields = append(fields, "created_ts")
args = append(args, create.CreatedTs)
}
if create.UpdatedTs != 0 {
fields = append(fields, "updated_ts")
args = append(args, create.UpdatedTs)
}

payload := "{}"
if create.Payload != nil {
payloadBytes, err := protojson.Marshal(create.Payload)
Expand All @@ -23,13 +33,12 @@ func (d *DB) CreateMemo(ctx context.Context, create *store.Memo) (*store.Memo, e
}
payload = string(payloadBytes)
}
args := []any{create.UID, create.CreatorID, create.Content, create.Visibility, payload}
fields = append(fields, "payload")
args = append(args, payload)

stmt := "INSERT INTO memo (" + strings.Join(fields, ", ") + ") VALUES (" + placeholders(len(args)) + ") RETURNING id, created_ts, updated_ts, row_status"
stmt := "INSERT INTO memo (" + strings.Join(fields, ", ") + ") VALUES (" + placeholders(len(args)) + ") RETURNING id, row_status"
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(
&create.ID,
&create.CreatedTs,
&create.UpdatedTs,
&create.RowStatus,
); err != nil {
return nil, err
Expand Down
19 changes: 16 additions & 3 deletions store/db/sqlite/memo.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ import (
func (d *DB) CreateMemo(ctx context.Context, create *store.Memo) (*store.Memo, error) {
fields := []string{"`uid`", "`creator_id`", "`content`", "`visibility`", "`payload`"}
placeholder := []string{"?", "?", "?", "?", "?"}
if create.CreatedTs != 0 {
fields = append(fields, "`created_ts`")
placeholder = append(placeholder, "?")
}
if create.UpdatedTs != 0 {
fields = append(fields, "`updated_ts`")
placeholder = append(placeholder, "?")
}

payload := "{}"
if create.Payload != nil {
payloadBytes, err := protojson.Marshal(create.Payload)
Expand All @@ -25,12 +34,16 @@ func (d *DB) CreateMemo(ctx context.Context, create *store.Memo) (*store.Memo, e
payload = string(payloadBytes)
}
args := []any{create.UID, create.CreatorID, create.Content, create.Visibility, payload}
if create.CreatedTs != 0 {
args = append(args, create.CreatedTs)
}
if create.UpdatedTs != 0 {
args = append(args, create.UpdatedTs)
}

stmt := "INSERT INTO `memo` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`, `created_ts`, `updated_ts`, `row_status`"
stmt := "INSERT INTO `memo` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`, `row_status`"
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(
&create.ID,
&create.CreatedTs,
&create.UpdatedTs,
&create.RowStatus,
); err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/ActivityCalendar/CalendarCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const CalendarCell = memo((props: CalendarCellProps) => {
const { day, maxCount, tooltipText, onClick, size = "default" } = props;

const handleClick = () => {
if (day.count > 0 && onClick) {
if (onClick) {
onClick(day.date);
}
};
Expand All @@ -31,7 +31,7 @@ export const CalendarCell = memo((props: CalendarCellProps) => {
sizeConfig.borderRadius,
smallExtraClasses,
);
const isInteractive = Boolean(onClick && day.count > 0);
const isInteractive = Boolean(onClick);
const ariaLabel = day.isSelected ? `${tooltipText} (selected)` : tooltipText;

if (!day.isCurrentMonth) {
Expand Down
7 changes: 5 additions & 2 deletions web/src/components/ActivityCalendar/MonthCalendar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { memo } from "react";
import { memo, useMemo } from "react";
import { useInstance } from "@/contexts/InstanceContext";
import { useMemoFilterContext } from "@/contexts/MemoFilterContext";
import { cn } from "@/lib/utils";
import { useTranslate } from "@/utils/i18n";
import { CalendarCell } from "./CalendarCell";
Expand All @@ -13,19 +14,21 @@ export const MonthCalendar = memo((props: MonthCalendarProps) => {
const { month, data, maxCount, size = "default", onClick, className } = props;
const t = useTranslate();
const { generalSetting } = useInstance();
const { getFiltersByFactor } = useMemoFilterContext();

const weekStartDayOffset = generalSetting.weekStartDayOffset;

const today = useTodayDate();
const weekDays = useWeekdayLabels();
const selectedDate = useMemo(() => getFiltersByFactor("displayTime")?.[0]?.value || "", [getFiltersByFactor]);

const { weeks, weekDays: rotatedWeekDays } = useCalendarMatrix({
month,
data,
weekDays,
weekStartDayOffset,
today,
selectedDate: "",
selectedDate: selectedDate,
});

const sizeConfig = size === "small" ? SMALL_CELL_SIZE : DEFAULT_CELL_SIZE;
Expand Down
12 changes: 12 additions & 0 deletions web/src/components/MemoEditor/components/EditorMetadata.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { FC } from "react";
import DateTimeInput from "@/components/DateTimeInput";
import { useTranslate } from "@/utils/i18n";
import { useEditorContext } from "../state";
import type { EditorMetadataProps } from "../types";
import AttachmentList from "./AttachmentList";
import LocationDisplay from "./LocationDisplay";
import RelationList from "./RelationList";

export const EditorMetadata: FC<EditorMetadataProps> = () => {
const t = useTranslate();
const { state, actions, dispatch } = useEditorContext();

return (
Expand All @@ -22,6 +25,15 @@ export const EditorMetadata: FC<EditorMetadataProps> = () => {
{state.metadata.location && (
<LocationDisplay location={state.metadata.location} onRemove={() => dispatch(actions.setMetadata({ location: undefined }))} />
)}
<div className="w-full flex flex-row justify-start items-center text-sm space-x-2">
<div className="flex items-center">
<span className="text-gray-400">{t("editor.created-at")}:</span>
<DateTimeInput
value={state.timestamps.createTime || new Date()}
onChange={(date) => dispatch(actions.setTimestamps({ createTime: date }))}
/>
</div>
</div>
</div>
);
};
16 changes: 13 additions & 3 deletions web/src/components/MemoEditor/hooks/useMemoInit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import dayjs from "dayjs";
import { useEffect, useRef } from "react";
import { useMemoFilterContext } from "@/contexts/MemoFilterContext";
import type { EditorRefActions } from "../Editor";
import { cacheService, memoService } from "../services";
import { useEditorContext } from "../state";
Expand All @@ -10,7 +12,8 @@ export const useMemoInit = (
username: string,
autoFocus?: boolean,
) => {
const { actions, dispatch } = useEditorContext();
const { state, actions, dispatch } = useEditorContext();
const { getFiltersByFactor } = useMemoFilterContext();
const initializedRef = useRef(false);

useEffect(() => {
Expand All @@ -32,7 +35,14 @@ export const useMemoInit = (
}),
);
} else {
// Load from cache for new memo
// New memo: first apply date filter if not already set, then load from cache
if (!state.timestamps.createTime) {
const displayTimeFilter = getFiltersByFactor("displayTime")?.[0]?.value;
if (displayTimeFilter) {
dispatch(actions.setTimestamps({ createTime: dayjs(displayTimeFilter).toDate() }));
}
}

const cachedContent = cacheService.load(cacheService.key(username, cacheKey));
if (cachedContent) {
dispatch(actions.updateContent(cachedContent));
Expand All @@ -52,5 +62,5 @@ export const useMemoInit = (
};

init();
}, [memoName, cacheKey, username, autoFocus, actions, dispatch, editorRef]);
}, [memoName, cacheKey, username, autoFocus, actions, dispatch, editorRef, getFiltersByFactor, state.timestamps.createTime]);
};
5 changes: 5 additions & 0 deletions web/src/components/MemoEditor/state/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export const editorActions = {
payload: metadata,
}),

setTimestamps: (timestamps: Partial<EditorState["timestamps"]>): EditorAction => ({
type: "SET_TIMESTAMPS",
payload: timestamps,
}),

addAttachment: (attachment: Attachment): EditorAction => ({
type: "ADD_ATTACHMENT",
payload: attachment,
Expand Down
9 changes: 9 additions & 0 deletions web/src/components/MemoEditor/state/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS
},
};

case "SET_TIMESTAMPS":
return {
...state,
timestamps: {
...state.timestamps,
...action.payload,
},
};

case "ADD_ATTACHMENT":
return {
...state,
Expand Down
1 change: 1 addition & 0 deletions web/src/components/MemoEditor/state/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type EditorAction =
| { type: "INIT_MEMO"; payload: { content: string; metadata: EditorState["metadata"]; timestamps: EditorState["timestamps"] } }
| { type: "UPDATE_CONTENT"; payload: string }
| { type: "SET_METADATA"; payload: Partial<EditorState["metadata"]> }
| { type: "SET_TIMESTAMPS"; payload: Partial<EditorState["timestamps"]> }
| { type: "ADD_ATTACHMENT"; payload: Attachment }
| { type: "REMOVE_ATTACHMENT"; payload: string }
| { type: "ADD_RELATION"; payload: MemoRelation }
Expand Down
20 changes: 16 additions & 4 deletions web/src/hooks/useDateFilterNavigation.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import { useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { stringifyFilters } from "@/contexts/MemoFilterContext";
import { MemoFilter, stringifyFilters, useMemoFilterContext } from "@/contexts/MemoFilterContext";

export const useDateFilterNavigation = () => {
const navigate = useNavigate();
const { filters } = useMemoFilterContext();

const navigateToDateFilter = useCallback(
(date: string) => {
const filterQuery = stringifyFilters([{ factor: "displayTime", value: date }]);
navigate(`/?filter=${filterQuery}`);
const otherFilters = filters.filter((f) => f.factor !== "displayTime");
const newFilters: MemoFilter[] = [...otherFilters];
const existingDateFilter = filters.find((f) => f.factor === "displayTime");

// If the selected date is different from the current filter, add the new filter.
// If the selected date is the same, the filter is effectively removed.
if (existingDateFilter?.value !== date) {
newFilters.push({ factor: "displayTime", value: date });
}

const filterQuery = stringifyFilters(newFilters);
const targetUrl = filterQuery ? `/?filter=${filterQuery}` : "/";
navigate(targetUrl);
},
[navigate],
[filters, navigate],
);

return navigateToDateFilter;
Expand Down
3 changes: 2 additions & 1 deletion web/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@
"no-changes-detected": "No changes detected",
"focus-mode": "Focus Mode",
"exit-focus-mode": "Exit Focus Mode",
"slash-commands": "Type `/` for commands"
"slash-commands": "Type `/` for commands",
"created-at": "Created at"
},
"filters": {
"has-code": "hasCode",
Expand Down