Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-3048] feat: added-stickies #6339

Merged
merged 15 commits into from
Jan 7, 2025
Merged
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
16 changes: 15 additions & 1 deletion apiserver/plane/app/views/workspace/preference.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
from plane.db.models import Workspace
from plane.app.serializers.workspace import WorkspaceHomePreferenceSerializer

# Django imports

from django.db.models import Count


# Third party imports
from rest_framework.response import Response
from rest_framework import status
Expand All @@ -28,20 +33,29 @@ def get(self, request, slug):

keys = [key for key, _ in WorkspaceHomePreference.HomeWidgetKeys.choices]

sort_order_counter = 1

for preference in keys:
if preference not in get_preference.values_list("key", flat=True):
create_preference_keys.append(preference)

sort_order = 1000 - sort_order_counter

preference = WorkspaceHomePreference.objects.bulk_create(
[
WorkspaceHomePreference(
key=key, user=request.user, workspace=workspace
key=key,
user=request.user,
workspace=workspace,
sort_order=sort_order,
)
for key in create_preference_keys
],
batch_size=10,
ignore_conflicts=True,
)
sort_order_counter += 1

preference = WorkspaceHomePreference.objects.filter(
user=request.user, workspace_id=workspace.id
)
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ export * from "./activity";
export * from "./epics";
export * from "./charts";
export * from "./home";
export * from "./stickies";
8 changes: 8 additions & 0 deletions packages/types/src/stickies.d copy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type TSticky = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate file

id: string;
name?: string;
description_html?: string;
color?: string;
createdAt?: Date;
updatedAt?: Date;
};
Comment on lines +1 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Unintended duplicate file detected

The file name stickies.d copy.ts suggests this is a duplicate or temporary copy. Including duplicate type definitions can cause confusion and potential conflicts.

Recommend removing this file if it's unnecessary or renaming it appropriately if it serves a unique purpose.

8 changes: 8 additions & 0 deletions packages/types/src/stickies.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type TSticky = {
id: string;
name?: string;
description_html?: string;
color?: string;
createdAt?: Date;
updatedAt?: Date;
};
Comment on lines +1 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding essential fields for data ownership and UI placement.

The TSticky type might benefit from additional fields:

  1. Workspace/user association fields for data ownership (e.g., workspace_id, created_by)
  2. Position/order fields for UI placement (e.g., position, order)
  3. Color validation (e.g., hex format)

Consider extending the type:

 export type TSticky = {
   id: string;
   name?: string;
   description_html?: string;
   color?: string;
+  workspace_id: string;
+  created_by: string;
+  position?: { x: number; y: number };
+  order?: number;
   createdAt?: Date;
   updatedAt?: Date;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type TSticky = {
id: string;
name?: string;
description_html?: string;
color?: string;
createdAt?: Date;
updatedAt?: Date;
};
export type TSticky = {
id: string;
name?: string;
description_html?: string;
color?: string;
workspace_id: string;
created_by: string;
position?: { x: number; y: number };
order?: number;
createdAt?: Date;
updatedAt?: Date;
};

2 changes: 2 additions & 0 deletions packages/ui/src/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ export * from "./overview-icon";
export * from "./on-track-icon";
export * from "./off-track-icon";
export * from "./at-risk-icon";
export * from "./multiple-sticky";
export * from "./sticky-note-icon";
28 changes: 28 additions & 0 deletions packages/ui/src/icons/multiple-sticky.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as React from "react";

import { ISvgIcons } from "./type";

export const RecentStickyIcon: React.FC<ISvgIcons> = ({ className = "text-current", ...rest }) => (
<svg
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these are icons, any reason to add the icons as component rather than import as assets of type SVG?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wanted to control the color, that's why making it component

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That can be also made while using svg as an asset and change color while using it.

width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
{...rest}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.23602 12.7638C2.60067 13.1285 3.09525 13.3333 3.61095 13.3333H9.72206C9.94307 13.3333 10.155 13.2455 10.3113 13.0892L13.0891 10.3115C13.2454 10.1552 13.3332 9.94323 13.3332 9.72221V3.6111C13.3332 3.0954 13.1283 2.60083 12.7637 2.23617C12.399 1.87152 11.9044 1.66666 11.3887 1.66666H3.61095C3.09525 1.66666 2.60067 1.87152 2.23602 2.23617C1.87136 2.60083 1.6665 3.0954 1.6665 3.6111V11.3889C1.6665 11.9046 1.87136 12.3992 2.23602 12.7638ZM11.0435 9.99999L9.99984 11.0437V10.2778C9.99984 10.2041 10.0291 10.1334 10.0812 10.0813C10.1333 10.0293 10.2039 9.99999 10.2776 9.99999H11.0435ZM8.33317 11.6667V10.2778C8.33317 9.76207 8.53803 9.26749 8.90269 8.90284C9.26734 8.53819 9.76192 8.33332 10.2776 8.33332H11.6665V3.6111C11.6665 3.53743 11.6372 3.46678 11.5851 3.41468C11.5331 3.36259 11.4624 3.33332 11.3887 3.33332H3.61095C3.53728 3.33332 3.46662 3.36259 3.41453 3.41468C3.36244 3.46678 3.33317 3.53743 3.33317 3.6111V11.3889C3.33317 11.4626 3.36244 11.5332 3.41453 11.5853C3.46662 11.6374 3.53728 11.6667 3.61095 11.6667H8.33317Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.36088 16.832C6.65667 17.2545 7.10816 17.5421 7.61603 17.6317L13.6343 18.6928C13.852 18.7312 14.0759 18.6816 14.257 18.5548L17.4749 16.3016C17.656 16.1748 17.7792 15.9813 17.8176 15.7636L18.8788 9.74538C18.9683 9.23752 18.8525 8.71488 18.5567 8.29244C18.2609 7.87001 17.8094 7.58238 17.3015 7.49283L15.1445 7.11249C14.6913 7.03257 14.2591 7.33521 14.1792 7.78846C14.0992 8.2417 14.4019 8.67392 14.8551 8.75384L17.0121 9.13417C17.0847 9.14697 17.1492 9.18806 17.1914 9.2484C17.2337 9.30875 17.2502 9.38341 17.2374 9.45597L16.4174 14.1064L15.0497 13.8653C14.5418 13.7757 14.0192 13.8916 13.5967 14.1874C13.1743 14.4832 12.8867 14.9347 12.7971 15.4425L12.5559 16.8103L7.90544 15.9903C7.83289 15.9775 7.76839 15.9364 7.72613 15.8761C7.68388 15.8157 7.66733 15.7411 7.68012 15.6685L7.77252 15.1445C7.85244 14.6913 7.5498 14.259 7.09655 14.1791C6.64331 14.0992 6.21109 14.4018 6.13117 14.8551L6.03877 15.3791C5.94922 15.887 6.06509 16.4096 6.36088 16.832ZM14.3054 16.4862L14.4384 15.7319C14.4512 15.6594 14.4923 15.5949 14.5527 15.5526C14.613 15.5104 14.6877 15.4938 14.7602 15.5066L15.5145 15.6396L14.3054 16.4862Z"
fill="currentColor"
/>
</svg>
);
36 changes: 36 additions & 0 deletions packages/ui/src/icons/sticky-note-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from "react";

import { ISvgIcons } from "./type";

export const StickyNoteIcon: React.FC<ISvgIcons> = ({ width = "17", height = "17", className, color }) => (
<svg
gakshita marked this conversation as resolved.
Show resolved Hide resolved
width={width}
height={height}
className={className}
viewBox="0 0 17 17"
fill={"currentColor"}
xmlns="http://www.w3.org/2000/svg"
style={{ color }}
>
<path
d="M11.9167 16.0833H2.75008C2.30805 16.0833 1.88413 15.9077 1.57157 15.5951C1.25901 15.2826 1.08341 14.8587 1.08341 14.4166V2.74996C1.08341 2.30793 1.25901 1.88401 1.57157 1.57145C1.88413 1.25889 2.30805 1.08329 2.75008 1.08329H14.4167C14.8588 1.08329 15.2827 1.25889 15.5953 1.57145C15.9078 1.88401 16.0834 2.30793 16.0834 2.74996V11.9166L11.9167 16.0833Z"
style={{ opacity: 0.5 }}
fill="currentColor"
/>
<path
d="M11.0834 16.0833V12.75C11.0834 12.3079 11.259 11.884 11.5716 11.5714C11.8841 11.2589 12.3081 11.0833 12.7501 11.0833H16.0834"
style={{ opacity: 0.5 }}
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1.27694 15.8898C1.66764 16.2805 2.19755 16.5 2.75008 16.5H11.9167C12.0273 16.5 12.1332 16.4561 12.2114 16.3779L16.378 12.2113C16.4562 12.1331 16.5001 12.0271 16.5001 11.9166V2.74996C16.5001 2.19742 16.2806 1.66752 15.8899 1.27682C15.4992 0.88612 14.9693 0.666626 14.4167 0.666626H2.75008C2.19755 0.666626 1.66764 0.88612 1.27694 1.27682C0.886241 1.66752 0.666748 2.19742 0.666748 2.74996V14.4166C0.666748 14.9692 0.886241 15.4991 1.27694 15.8898ZM15.6667 11.5V11.744L11.7442 15.6666H11.5001V12.75C11.5001 12.4184 11.6318 12.1005 11.8662 11.8661C12.1006 11.6317 12.4186 11.5 12.7501 11.5H15.6667ZM10.6667 15.6666V12.75C10.6667 12.1974 10.8862 11.6675 11.2769 11.2768C11.6676 10.8861 12.1975 10.6666 12.7501 10.6666H15.6667V2.74996C15.6667 2.41844 15.5351 2.1005 15.3006 1.86608C15.0662 1.63166 14.7483 1.49996 14.4167 1.49996H2.75008C2.41856 1.49996 2.10062 1.63166 1.8662 1.86608C1.63178 2.1005 1.50008 2.41844 1.50008 2.74996V14.4166C1.50008 14.7481 1.63178 15.0661 1.8662 15.3005C2.10062 15.5349 2.41856 15.6666 2.75008 15.6666H10.6667Z"
fill="currentColor"
/>
<path
d="M11.0816 12.7499C11.0491 13.2706 11.0816 16.0833 11.0816 16.0833H11.9149L16.0816 11.9166V11.0833H12.7483C11.5001 11.0833 11.1141 12.2293 11.0816 12.7499Z"
fill="currentColor"
/>
</svg>
);
1 change: 0 additions & 1 deletion web/ce/components/stickies/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion web/ce/components/stickies/widget.tsx

This file was deleted.

1 change: 1 addition & 0 deletions web/core/components/editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./embeds";
export * from "./lite-text-editor";
export * from "./pdf";
export * from "./rich-text-editor";
export * from "./sticky-editor";
36 changes: 36 additions & 0 deletions web/core/components/editor/sticky-editor/color-pallete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { TSticky } from "@plane/types";

export const STICKY_COLORS = [
"#D4DEF7", // light periwinkle
"#B4E4FF", // light blue
"#FFF2B4", // light yellow
"#E3E3E3", // light gray
"#FFE2DD", // light pink
"#F5D1A5", // light orange
"#D1F7C4", // light green
"#E5D4FF", // light purple
];

type TProps = {
handleUpdate: (data: Partial<TSticky>) => Promise<void>;
};

export const ColorPalette = (props: TProps) => {
const { handleUpdate } = props;
return (
<div className="absolute z-10 bottom-5 left-0 w-56 shadow p-2 rounded-md bg-custom-background-100 mb-2">
<div className="text-sm font-semibold text-custom-text-400 mb-2">Background colors</div>
<div className="flex flex-wrap gap-2">
{STICKY_COLORS.map((color, index) => (
<button
key={index}
type="button"
onClick={() => handleUpdate({ color })}
className="h-6 w-6 rounded-md hover:ring-2 hover:ring-custom-primary focus:outline-none focus:ring-2 focus:ring-custom-primary transition-all"
style={{ backgroundColor: color }}
/>
))}
Comment on lines +24 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve button accessibility and React key usage.

The buttons need aria-labels for accessibility, and using index as key might cause issues with React reconciliation.

-        {STICKY_COLORS.map((color, index) => (
+        {Object.entries(STICKY_COLORS).map(([name, color]) => (
           <button
-            key={index}
+            key={color}
             type="button"
             onClick={() => handleUpdate({ color })}
+            aria-label={`Set sticky note color to ${name.toLowerCase()}`}
             className="h-6 w-6 rounded-md hover:ring-2 hover:ring-custom-primary focus:outline-none focus:ring-2 focus:ring-custom-primary transition-all"
             style={{ backgroundColor: color }}
           />
         ))}

Committable suggestion skipped: line range outside the PR's diff.

</div>
</div>
);
};
109 changes: 109 additions & 0 deletions web/core/components/editor/sticky-editor/editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { useState } from "react";
// plane constants
import { EIssueCommentAccessSpecifier } from "@plane/constants";
// plane editor
import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef } from "@plane/editor";
// components
import { TSticky } from "@plane/types";
// helpers
import { cn } from "@/helpers/common.helper";
import { getEditorFileHandlers } from "@/helpers/editor.helper";
// hooks
// plane web hooks
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
import { useFileSize } from "@/plane-web/hooks/use-file-size";
import { Toolbar } from "./toolbar";

interface StickyEditorWrapperProps
extends Omit<ILiteTextEditor, "disabledExtensions" | "fileHandler" | "mentionHandler"> {
workspaceSlug: string;
workspaceId: string;
projectId?: string;
accessSpecifier?: EIssueCommentAccessSpecifier;
handleAccessChange?: (accessKey: EIssueCommentAccessSpecifier) => void;
showAccessSpecifier?: boolean;
showSubmitButton?: boolean;
isSubmitting?: boolean;
showToolbarInitially?: boolean;
showToolbar?: boolean;
uploadFile: (file: File) => Promise<string>;
parentClassName?: string;
handleColorChange: (data: Partial<TSticky>) => Promise<void>;
handleDelete: () => Promise<void>;
}

export const StickyEditor = React.forwardRef<EditorRefApi, StickyEditorWrapperProps>((props, ref) => {
const {
containerClassName,
workspaceSlug,
workspaceId,
projectId,
handleDelete,
handleColorChange,
showToolbarInitially = true,
showToolbar = true,
parentClassName = "",
placeholder = "Add comment...",
uploadFile,
...rest
} = props;
// states
const [isFocused, setIsFocused] = useState(showToolbarInitially);
// editor flaggings
const { liteTextEditor: disabledExtensions } = useEditorFlagging(workspaceSlug?.toString());
// file size
const { maxFileSize } = useFileSize();
function isMutableRefObject<T>(ref: React.ForwardedRef<T>): ref is React.MutableRefObject<T | null> {
return !!ref && typeof ref === "object" && "current" in ref;
}
// derived values
const editorRef = isMutableRefObject<EditorRefApi>(ref) ? ref.current : null;

return (
<div
className={cn("relative border border-custom-border-200 rounded p-3", parentClassName)}
onFocus={() => !showToolbarInitially && setIsFocused(true)}
onBlur={() => !showToolbarInitially && setIsFocused(false)}
>
<LiteTextEditorWithRef
ref={ref}
disabledExtensions={[...disabledExtensions, "enter-key"]}
fileHandler={getEditorFileHandlers({
maxFileSize,
projectId,
uploadFile,
workspaceId,
workspaceSlug,
})}
mentionHandler={{
renderComponent: () => <></>,
}}
placeholder={placeholder}
containerClassName={cn(containerClassName, "relative")}
{...rest}
/>
<div
className={cn(
"transition-all duration-300 ease-out origin-top",
isFocused ? "max-h-[200px] opacity-100 scale-y-100 mt-3" : "max-h-0 opacity-0 scale-y-0 invisible"
)}
>
<Toolbar
executeCommand={(item) => {
// TODO: update this while toolbar homogenization
// @ts-expect-error type mismatch here
editorRef?.executeMenuItemCommand({
Comment on lines +93 to +95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

TODO comment and type assertion need attention.

There's a TODO comment about toolbar homogenization and a type assertion that needs to be addressed.

This could lead to runtime errors. Consider:

  1. Creating proper types for the toolbar commands
  2. Adding runtime checks for the command structure
interface EditorCommand {
  itemKey: string;
  extraProps?: Record<string, unknown>;
}

function isValidCommand(command: unknown): command is EditorCommand {
  return typeof command === 'object' && command !== null && 'itemKey' in command;
}

itemKey: item.itemKey,
...item.extraProps,
});
}}
handleDelete={handleDelete}
handleColorChange={handleColorChange}
editorRef={editorRef}
/>
</div>
</div>
);
});

StickyEditor.displayName = "StickyEditor";
2 changes: 2 additions & 0 deletions web/core/components/editor/sticky-editor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./editor";
export * from "./toolbar";
Loading
Loading