diff --git a/design.config.json b/design.config.json new file mode 100644 index 0000000..012a87a --- /dev/null +++ b/design.config.json @@ -0,0 +1,7 @@ +{ + "nodedc": { + "accent_rgb": [195, 255, 102], + "passive_card_rgb": [42, 43, 46], + "active_card_rgb": [195, 255, 102] + } +} diff --git a/plane-src/apps/api/plane/settings/common.py b/plane-src/apps/api/plane/settings/common.py index 9d651bd..4ceaa20 100644 --- a/plane-src/apps/api/plane/settings/common.py +++ b/plane-src/apps/api/plane/settings/common.py @@ -23,11 +23,25 @@ from plane.utils.url import is_valid_url BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +def env_flag(value, default=False): + if value is None: + return default + + normalized = str(value).strip().lower() + if normalized in {"1", "true", "yes", "on", "debug"}: + return True + if normalized in {"0", "false", "no", "off", "release", "prod", "production"}: + return False + + return default + + # Secret Key SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key()) # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = int(os.environ.get("DEBUG", "0")) +DEBUG = env_flag(os.environ.get("DEBUG", "0")) # Self-hosted mode IS_SELF_MANAGED = True diff --git a/plane-src/apps/api/plane/settings/production.py b/plane-src/apps/api/plane/settings/production.py index 7f3f90d..1c4730c 100644 --- a/plane-src/apps/api/plane/settings/production.py +++ b/plane-src/apps/api/plane/settings/production.py @@ -9,7 +9,7 @@ import os from .common import * # noqa # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = int(os.environ.get("DEBUG", 0)) == 1 +DEBUG = env_flag(os.environ.get("DEBUG", 0), default=False) # Honor the 'X-Forwarded-Proto' header for request.is_secure() SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") diff --git a/plane-src/apps/web/ce/components/issues/header.tsx b/plane-src/apps/web/ce/components/issues/header.tsx index 6c1c7d0..1d9cf54 100644 --- a/plane-src/apps/web/ce/components/issues/header.tsx +++ b/plane-src/apps/web/ce/components/issues/header.tsx @@ -108,7 +108,7 @@ export const IssuesHeader = observer(function IssuesHeader() { )} -
+
{ toggleCreateIssueModal(true, EIssuesStoreType.PROJECT); }} diff --git a/plane-src/apps/web/core/components/issues/filters.tsx b/plane-src/apps/web/core/components/issues/filters.tsx index 0bedecf..3528433 100644 --- a/plane-src/apps/web/core/components/issues/filters.tsx +++ b/plane-src/apps/web/core/components/issues/filters.tsx @@ -94,47 +94,54 @@ export const HeaderFilters = observer(function HeaderFilters(props: Props) { projectDetails={currentProjectDetails ?? undefined} isEpic={storeType === EIssuesStoreType.EPIC} /> -
- handleLayoutChange(layout)} - selectedLayout={activeLayout} - /> +
+
+ handleLayoutChange(layout)} + selectedLayout={activeLayout} + /> +
+
+ handleLayoutChange(layout)} + activeLayout={activeLayout} + /> +
+ + } + title={t("common.display")} + placement="bottom-end" + > + + + {canUserCreateIssue ? ( + + ) : ( + <> + )}
-
- handleLayoutChange(layout)} - activeLayout={activeLayout} - /> -
- - } - title={t("common.display")} - placement="bottom-end" - > - - - {canUserCreateIssue ? ( - - ) : ( - <> - )} ); }); diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/filters/header/display-filters/display-properties.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/filters/header/display-filters/display-properties.tsx index 3eeccb5..612c6b1 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/filters/header/display-filters/display-properties.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/filters/header/display-filters/display-properties.tsx @@ -71,10 +71,10 @@ export const FilterDisplayProperties = observer(function FilterDisplayProperties ))} diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/filters/header/mobile-layout-selection.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/filters/header/mobile-layout-selection.tsx index 14f7490..31e50bb 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/filters/header/mobile-layout-selection.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/filters/header/mobile-layout-selection.tsx @@ -29,10 +29,12 @@ export function MobileLayoutSelection({ className="flex flex-grow justify-center text-13 text-secondary" placement="bottom-start" customButton={ - } diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/block.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/block.tsx index a691a43..0cea8da 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/block.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/block.tsx @@ -312,12 +312,14 @@ export const KanbanIssueBlock = observer(function KanbanIssueBlock(props: IssueB : "rounded-lg border border-subtle bg-layer-2 p-3 shadow-raised-100 outline-[0.5px] outline-transparent hover:border-strong hover:shadow-raised-200", { "hover:cursor-pointer": isDragAllowed }, { - "bg-[#C3FF66] text-[#111111]": cardVariant === "internal-contour" && isPeeked, - "bg-[#2A2B2E] text-white": cardVariant === "internal-contour" && !isPeeked, + "bg-[rgb(var(--nodedc-card-active-rgb))] text-[#111111]": + cardVariant === "internal-contour" && isPeeked, + "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white": + cardVariant === "internal-contour" && !isPeeked, "border border-accent-strong hover:border-accent-strong": cardVariant !== "internal-contour" && isPeeked, }, { "z-[100] bg-layer-1": isCurrentBlockDragging && cardVariant !== "internal-contour" }, - { "z-[100] bg-[#C3FF66]": isCurrentBlockDragging && cardVariant === "internal-contour" } + { "z-[100] bg-[rgb(var(--nodedc-card-active-rgb))]": isCurrentBlockDragging && cardVariant === "internal-contour" } )} onClick={() => handleIssuePeekOverview(issue)} disabled={!!issue?.tempId} diff --git a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/internal-contour-card.tsx b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/internal-contour-card.tsx index cc8bce9..ecc996b 100644 --- a/plane-src/apps/web/core/components/issues/issue-layouts/kanban/internal-contour-card.tsx +++ b/plane-src/apps/web/core/components/issues/issue-layouts/kanban/internal-contour-card.tsx @@ -76,8 +76,9 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban const projectStateIds = issue.project_id ? getProjectStateIds(issue.project_id) : []; const foregroundClasses = isActive ? "text-[#111111]" : "text-white"; const subtleTextClasses = isActive ? "text-[#2F4721]" : "text-[#B3B3B8]"; - const pillBackgroundClasses = isActive ? "bg-black/10 text-[#111111]" : "bg-[#1B1B1F] text-white"; - const iconBubbleClasses = isActive ? "bg-black text-[#C3FF66]" : "bg-[#111214] text-white"; + const pillBackgroundClasses = + isActive ? "bg-black/10 text-[#111111]" : "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white"; + const iconBubbleClasses = isActive ? "bg-black text-[rgb(var(--nodedc-card-active-rgb))]" : "bg-[#111214] text-white"; const statusIconColor = selectedState?.color ?? (isActive ? "#111111" : "var(--text-color-primary)"); const handleEventPropagation = (e: React.MouseEvent) => { @@ -95,7 +96,9 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban ref={menuActionRef} className={cn( "flex h-8 w-8 cursor-pointer items-center justify-center rounded-full p-1 transition-colors", - isActive ? "bg-black text-[#C3FF66] hover:bg-black/90" : "bg-[#111214] text-white hover:bg-[#0A0B0C]", + isActive + ? "bg-black text-[rgb(var(--nodedc-card-active-rgb))] hover:bg-black/90" + : "bg-[#111214] text-white hover:bg-[#0A0B0C]", isMenuActive && (isActive ? "bg-black/90" : "bg-[#0A0B0C]") )} onClick={() => setIsMenuActive(!isMenuActive)} diff --git a/plane-src/apps/web/core/components/rich-filters/add-filters/button.tsx b/plane-src/apps/web/core/components/rich-filters/add-filters/button.tsx index 51f58c3..cab723b 100644 --- a/plane-src/apps/web/core/components/rich-filters/add-filters/button.tsx +++ b/plane-src/apps/web/core/components/rich-filters/add-filters/button.tsx @@ -68,12 +68,14 @@ export const AddFilterButton = observer(function AddFilterButton

- {iconConfig.shouldShowIcon && } +

+ {iconConfig.shouldShowIcon && ( + + )} {label}
} diff --git a/plane-src/apps/web/core/components/rich-filters/add-filters/dropdown.tsx b/plane-src/apps/web/core/components/rich-filters/add-filters/dropdown.tsx index 505162b..2750aea 100644 --- a/plane-src/apps/web/core/components/rich-filters/add-filters/dropdown.tsx +++ b/plane-src/apps/web/core/components/rich-filters/add-filters/dropdown.tsx @@ -12,6 +12,7 @@ import type { IFilterInstance } from "@plane/shared-state"; import type { TExternalFilter, TFilterProperty, TSupportedOperators } from "@plane/types"; import { CustomSearchSelect } from "@plane/ui"; import { getOperatorForPayload } from "@plane/utils"; +import { localizeRichFilterLabel } from "../i18n"; export type TAddFilterDropdownProps

= { customButton: React.ReactNode; @@ -40,12 +41,12 @@ export const AddFilterDropdown = observer(function AddFilterDropdown< {config.icon && ( )} - {config.label} + {localizeRichFilterLabel(config.label)}

{config.rightContent}
), - query: config.label.toLowerCase(), + query: localizeRichFilterLabel(config.label).toLowerCase(), })); // If all filters are applied, show disabled options @@ -54,8 +55,8 @@ export const AddFilterDropdown = observer(function AddFilterDropdown< ? [ { value: "all_filters_applied", - content:
All filters applied
, - query: "all filters applied", + content:
Все фильтры уже применены
, + query: "все фильтры уже применены", disabled: true, }, ] diff --git a/plane-src/apps/web/core/components/rich-filters/filter-item/close-button.tsx b/plane-src/apps/web/core/components/rich-filters/filter-item/close-button.tsx index 753fec4..62707ab 100644 --- a/plane-src/apps/web/core/components/rich-filters/filter-item/close-button.tsx +++ b/plane-src/apps/web/core/components/rich-filters/filter-item/close-button.tsx @@ -29,7 +29,7 @@ export const FilterItemCloseButton = observer(function FilterItemCloseButton< return (
} /> diff --git a/plane-src/apps/web/core/components/rich-filters/filter-value-input/select/selected-options-display.tsx b/plane-src/apps/web/core/components/rich-filters/filter-value-input/select/selected-options-display.tsx index 61520a6..1737e3e 100644 --- a/plane-src/apps/web/core/components/rich-filters/filter-value-input/select/selected-options-display.tsx +++ b/plane-src/apps/web/core/components/rich-filters/filter-value-input/select/selected-options-display.tsx @@ -58,7 +58,7 @@ export function SelectedOptionsDisplay(props: TSelectedO enterTo="opacity-100" className="ml-1 whitespace-nowrap text-tertiary" > - +{remainingCount} more + +{remainingCount} еще )} diff --git a/plane-src/apps/web/core/components/rich-filters/filter-value-input/select/shared.tsx b/plane-src/apps/web/core/components/rich-filters/filter-value-input/select/shared.tsx index a948b26..ba779f3 100644 --- a/plane-src/apps/web/core/components/rich-filters/filter-value-input/select/shared.tsx +++ b/plane-src/apps/web/core/components/rich-filters/filter-value-input/select/shared.tsx @@ -51,7 +51,7 @@ export const getFormattedOptions = (options: IFilterOpti export const getCommonCustomSearchSelectProps = (isDisabled?: boolean) => ({ customButtonClassName: cn( - "h-full w-full px-2 text-13 font-regular transition-all duration-300 ease-in-out", + "h-full w-full px-3 text-13 font-regular transition-all duration-300 ease-in-out", !isDisabled && COMMON_FILTER_ITEM_BORDER_CLASSNAME, isDisabled && "hover:bg-surface-1" ), diff --git a/plane-src/apps/web/core/components/rich-filters/filters-row.tsx b/plane-src/apps/web/core/components/rich-filters/filters-row.tsx index 6a63603..0c19cb9 100644 --- a/plane-src/apps/web/core/components/rich-filters/filters-row.tsx +++ b/plane-src/apps/web/core/components/rich-filters/filters-row.tsx @@ -9,6 +9,7 @@ import { observer } from "mobx-react"; import { ListFilterPlus } from "lucide-react"; import { Transition } from "@headlessui/react"; // plane imports +import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/propel/button"; import type { IFilterInstance } from "@plane/shared-state"; import type { TExternalFilter, TFilterProperty } from "@plane/types"; @@ -40,6 +41,7 @@ export const FiltersRow = observer(function FiltersRow["buttonConfig"]> = { - label: !hasAnyConditions ? "Filters" : null, + label: !hasAnyConditions ? t("common.filters") : null, }; const handleUpdate = useCallback(async () => { @@ -92,44 +94,44 @@ export const FiltersRow = observer(function FiltersRow ); const mainContent = ( -
+
{leftContent}
{rightContent} @@ -137,12 +139,10 @@ export const FiltersRow = observer(function FiltersRow ); - const ModalVariant = ( -
{mainContent}
- ); + const ModalVariant =
{mainContent}
; const HeaderVariant = ( -
+
{mainContent}
); @@ -160,7 +160,7 @@ export const FiltersRow = observer(function FiltersRow{variant === "modal" ? ModalVariant : HeaderVariant}; }); -const COMMON_OPERATION_BUTTON_CLASSNAME = "py-1"; +const COMMON_OPERATION_BUTTON_CLASSNAME = "min-h-9 px-4 py-1"; type TElementTransitionProps = { children: React.ReactNode; diff --git a/plane-src/apps/web/core/components/rich-filters/filters-toggle.tsx b/plane-src/apps/web/core/components/rich-filters/filters-toggle.tsx index 2747921..5764c52 100644 --- a/plane-src/apps/web/core/components/rich-filters/filters-toggle.tsx +++ b/plane-src/apps/web/core/components/rich-filters/filters-toggle.tsx @@ -19,7 +19,7 @@ type TFiltersToggleProps

= }; const COMMON_CLASSNAME = - "grid place-items-center h-7 w-full py-0.5 px-2 rounded-md border border-subtle-1 transition-all duration-200 cursor-pointer"; + "nodedc-toolbar-filter-toggle grid h-8 w-8 place-items-center px-0 py-0 transition-all duration-200 cursor-pointer"; export const FiltersToggle = observer(function FiltersToggle

( props: TFiltersToggleProps @@ -40,26 +40,13 @@ export const FiltersToggle = observer(function FiltersToggle

); }); diff --git a/plane-src/apps/web/core/components/rich-filters/i18n.ts b/plane-src/apps/web/core/components/rich-filters/i18n.ts new file mode 100644 index 0000000..79b69cb --- /dev/null +++ b/plane-src/apps/web/core/components/rich-filters/i18n.ts @@ -0,0 +1,35 @@ +const FILTER_LABEL_MAP: Record = { + Assignees: "Назначенные", + Assignee: "Назначенный", + Priority: "Приоритет", + State: "Статус", + "State Group": "Группа статусов", + "State Groups": "Группы статусов", + Mentions: "Упоминания", + Label: "Метка", + Labels: "Метки", + Project: "Проект", + "Created by": "Автор", + "Created at": "Дата создания", + "Updated at": "Дата обновления", + "Start date": "Дата начала", + "Target date": "Срок выполнения", + "Due date": "Срок выполнения", + Parent: "Родительский", +}; + +const FILTER_OPERATOR_MAP: Record = { + is: "равно", + "is any of": "содержит одно из", + between: "между", +}; + +export const localizeRichFilterLabel = (label: string | undefined) => { + if (!label) return ""; + return FILTER_LABEL_MAP[label] ?? label; +}; + +export const localizeRichFilterOperator = (label: string | undefined) => { + if (!label) return ""; + return FILTER_OPERATOR_MAP[label] ?? label; +}; diff --git a/plane-src/apps/web/core/components/rich-filters/shared.ts b/plane-src/apps/web/core/components/rich-filters/shared.ts index 498ac42..d35cc78 100644 --- a/plane-src/apps/web/core/components/rich-filters/shared.ts +++ b/plane-src/apps/web/core/components/rich-filters/shared.ts @@ -12,7 +12,7 @@ import type { TSupportedFilterFieldConfigs, } from "@plane/types"; -export const COMMON_FILTER_ITEM_BORDER_CLASSNAME = "border-r border-subtle-1"; +export const COMMON_FILTER_ITEM_BORDER_CLASSNAME = "nodedc-filter-chip-separator"; export const EMPTY_FILTER_PLACEHOLDER_TEXT = "--"; diff --git a/plane-src/apps/web/styles/globals.css b/plane-src/apps/web/styles/globals.css index e101c3a..151e916 100644 --- a/plane-src/apps/web/styles/globals.css +++ b/plane-src/apps/web/styles/globals.css @@ -27,6 +27,9 @@ --editor-colors-light-blue-background: #c5eff9; --editor-colors-dark-blue-background: #c9dafb; --editor-colors-purple-background: #e3d8fd; + --nodedc-accent-rgb: 51 163 255; + --nodedc-card-passive-rgb: 42 43 46; + --nodedc-card-active-rgb: 195 255 102; /* end background colors */ } /* background colors */ @@ -354,6 +357,169 @@ @apply bg-white/6; } + .nodedc-toolbar-group { + background: rgba(8, 8, 11, 0.92); + border-radius: 999px; + padding: 0.25rem; + border: 0 !important; + box-shadow: none !important; + isolation: isolate; + overflow: hidden; + } + + .nodedc-toolbar-icon-button { + border: 0 !important; + outline: none !important; + box-shadow: none !important; + border-radius: 999px !important; + background: transparent !important; + color: rgba(255, 255, 255, 0.72); + transition: + color 160ms ease, + background-color 160ms ease, + transform 160ms ease; + } + + .nodedc-toolbar-icon-button:hover { + background: rgba(255, 255, 255, 0.05) !important; + color: rgba(255, 255, 255, 0.94); + } + + .nodedc-toolbar-icon-button[data-active="true"] { + background: transparent !important; + color: rgba(255, 255, 255, 0.98); + } + + .nodedc-toolbar-icon-active-dot { + display: grid; + height: 2rem; + width: 2rem; + place-items: center; + border-radius: 999px; + transition: + background-color 160ms ease, + color 160ms ease; + } + + .nodedc-toolbar-icon-button[data-active="true"] .nodedc-toolbar-icon-active-dot { + background: rgb(var(--nodedc-accent-rgb)); + color: #0b1117; + } + + .nodedc-toolbar-pill { + position: relative; + display: inline-flex !important; + align-items: center; + justify-content: center; + flex-shrink: 0; + isolation: isolate; + overflow: hidden; + border: 0 !important; + outline: none !important; + box-shadow: none !important; + border-radius: 999px !important; + min-height: 2.5rem; + padding-inline: 1rem; + background: rgba(18, 18, 22, 0.94) !important; + color: var(--text-color-primary) !important; + transition: + background-color 160ms ease, + color 160ms ease, + transform 160ms ease; + } + + .nodedc-toolbar-pill:hover { + background: rgba(24, 24, 29, 0.96) !important; + } + + .nodedc-toolbar-pill[data-active="true"] { + color: rgb(var(--nodedc-accent-rgb)) !important; + } + + .nodedc-toolbar-primary { + border: 0 !important; + outline: none !important; + box-shadow: none !important; + border-radius: 999px !important; + min-height: 2.5rem; + padding-inline: 1rem; + background: rgb(var(--nodedc-accent-rgb)) !important; + color: #0b1117 !important; + } + + .nodedc-toolbar-primary:hover { + background: rgba(var(--nodedc-accent-rgb), 0.92) !important; + } + + .nodedc-toolbar-filter-toggle { + display: grid; + height: 2.5rem !important; + width: 2.5rem !important; + place-items: center; + flex-shrink: 0; + border: 0 !important; + outline: none !important; + box-shadow: none !important; + border-radius: 999px !important; + background: transparent !important; + color: rgba(255, 255, 255, 0.72) !important; + transition: color 160ms ease; + } + + .nodedc-toolbar-filter-toggle:hover, + .nodedc-toolbar-filter-toggle:focus-visible { + background: transparent !important; + color: rgba(255, 255, 255, 0.9) !important; + } + + .nodedc-toolbar-filter-toggle[data-active="true"] { + color: rgb(var(--nodedc-accent-rgb)) !important; + } + + .nodedc-filter-row-shell { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%), + rgba(8, 8, 11, 0.84); + border: 0 !important; + border-radius: 1.35rem !important; + -webkit-backdrop-filter: blur(20px); + backdrop-filter: blur(20px); + box-shadow: none !important; + outline: none !important; + } + + .nodedc-top-toolbar-cluster > * { + flex-shrink: 0; + } + + .nodedc-filter-chip { + border: 0 !important; + border-radius: 999px !important; + background: rgba(255, 255, 255, 0.045) !important; + color: var(--text-color-primary) !important; + -webkit-backdrop-filter: blur(18px); + backdrop-filter: blur(18px); + box-shadow: none !important; + outline: none !important; + } + + .nodedc-filter-chip-separator { + border-right: 1px solid rgba(255, 255, 255, 0.08); + } + + .nodedc-filter-clear-button { + border: 0 !important; + outline: none !important; + box-shadow: none !important; + border-radius: 999px !important; + background: rgba(255, 255, 255, 0.05) !important; + color: var(--text-color-primary) !important; + } + + .nodedc-filter-clear-button:hover { + background: rgba(255, 255, 255, 0.08) !important; + } + .nodedc-calendar-shell { @apply rounded-[1.1rem] border-0 bg-transparent p-1 shadow-none outline-none; } diff --git a/plane-src/packages/ui/src/dropdowns/custom-search-select.tsx b/plane-src/packages/ui/src/dropdowns/custom-search-select.tsx index b988a06..c0eccb7 100644 --- a/plane-src/packages/ui/src/dropdowns/custom-search-select.tsx +++ b/plane-src/packages/ui/src/dropdowns/custom-search-select.tsx @@ -40,7 +40,7 @@ export function CustomSearchSelect(props: ICustomSearchSelectProps) { optionsClassName = "", value, tabIndex, - noResultsMessage = "No matches found", + noResultsMessage = "Совпадений не найдено", defaultOpen = false, } = props; const [query, setQuery] = useState(""); @@ -104,14 +104,14 @@ export function CustomSearchSelect(props: ICustomSearchSelectProps) {