diff --git a/dist/img/icon/default/date.svg b/dist/img/icon/default/date.svg new file mode 100644 index 0000000000..3b7acf787d --- /dev/null +++ b/dist/img/icon/default/date.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 95e41e9752..0fd597d588 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "react-dom": "^17.0.2", "react-minimal-pie-chart": "^8.3.0", "react-pdf": "^6.2.2", - "react-router-dom": "^5.2.0", + "react-router-dom": "^5.3.4", "react-sortable-hoc": "^2.0.0", "react-virtualized": "^9.22.3", "regedit": "^5.1.3", @@ -87,7 +87,7 @@ "@types/raf": "^3.4.0", "@types/react": "^16.14.31", "@types/react-dom": "^16.9.16", - "@types/react-router-dom": "^4.3.5", + "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "cross-env": "^7.0.2", @@ -2691,12 +2691,12 @@ } }, "node_modules/@types/react-router-dom": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.5.tgz", - "integrity": "sha512-eFajSUASYbPHg2BDM1G8Btx+YqGgvROPIg6sBhl3O4kbDdYXdFdfrgQFf/pcBuQVObjfT9AL/dd15jilR5DIEA==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", "dev": true, "dependencies": { - "@types/history": "*", + "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router": "*" } diff --git a/package.json b/package.json index d70c6a2ff2..a9f9a98755 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@types/raf": "^3.4.0", "@types/react": "^16.14.31", "@types/react-dom": "^16.9.16", - "@types/react-router-dom": "^4.3.5", + "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "cross-env": "^7.0.2", @@ -141,7 +141,7 @@ "react-dom": "^17.0.2", "react-minimal-pie-chart": "^8.3.0", "react-pdf": "^6.2.2", - "react-router-dom": "^5.2.0", + "react-router-dom": "^5.3.4", "react-sortable-hoc": "^2.0.0", "react-virtualized": "^9.22.3", "regedit": "^5.1.3", diff --git a/src/img/arrow/dateSelectLight.svg b/src/img/arrow/dateSelectLight.svg new file mode 100644 index 0000000000..7a51b0f8b2 --- /dev/null +++ b/src/img/arrow/dateSelectLight.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/img/icon/mention.svg b/src/img/icon/mention.svg new file mode 100644 index 0000000000..2fe792b284 --- /dev/null +++ b/src/img/icon/mention.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/img/icon/sortArrow.svg b/src/img/icon/sortArrow.svg new file mode 100644 index 0000000000..47acff2910 --- /dev/null +++ b/src/img/icon/sortArrow.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/json/relation.ts b/src/json/relation.ts index 4e93a9350c..bdfd56a298 100644 --- a/src/json/relation.ts +++ b/src/json/relation.ts @@ -22,7 +22,8 @@ export default { 'sizeInBytes', 'restrictions', 'defaultTemplateId', - 'createdDate' + 'createdDate', + 'timestamp', ], sidebar: [ @@ -49,7 +50,8 @@ export default { 'restrictions', 'source', 'lastModifiedDate', - 'lastOpenedDate' + 'lastOpenedDate', + 'timestamp' ], relation: [ diff --git a/src/json/text.json b/src/json/text.json index 16f67d7e39..a99dc7c02a 100644 --- a/src/json/text.json +++ b/src/json/text.json @@ -89,6 +89,7 @@ "commonComment": "Comment", "commonIcon": "Icon", "commonObjects": "Objects", + "commonDates": "Dates", "commonPreferences": "Preferences", "commonDuplicate": "Duplicate", "commonRemoveFromFavorites": "Remove from Favorites", @@ -725,9 +726,12 @@ "popupSettingsPersonalSidebar": "Automatically show and hide sidebar", "popupSettingsPersonalSidebarMode": "Sidebar mode", "popupSettingsPersonalRelativeDates": "Display relative dates", + "popupSettingsPersonalDateFormat": "Date format", "popupSettingsPersonalSectionLanguage": "Language & Spelling", "popupSettingsPersonalSectionEditor": "Editor Personalisation", "popupSettingsPersonalSectionApp": "App Appearance", + "popupSettingsPersonalSectionDateTime": "Date & Time", + "popupSettingsPersonalTimeFormat": "Time format", "popupSettingsColorMode": "Color mode", "popupSettingsColorModeButtonLight": "Light", @@ -2198,6 +2202,9 @@ "participantPermissions2": "Owner", "participantPermissions3": "No access", + "relationMentions": "Mentioned in", + "relationCreator": "Created by", + "spaceStatus0": "Active", "spaceStatus1": "Loading", "spaceStatus2": "Ok", @@ -2258,4 +2265,5 @@ "formulaCheckboxNotEmptyShort": "Checked", "formulaCheckboxPercentEmpty": "Percentage unchecked", "formulaCheckboxPercentNotEmpty": "Percentage checked" + } diff --git a/src/json/theme.ts b/src/json/theme.ts index 220bb858a0..24fb6933c6 100644 --- a/src/json/theme.ts +++ b/src/json/theme.ts @@ -60,6 +60,11 @@ export default { text: '#b6b6b6', bg: '#f2f2f2' }, + + progress: { + bg: '#ebebeb', + fg: '#ffd15b', + } }, dark: { @@ -109,6 +114,11 @@ export default { text: '#9a9a9a', bg: '#b6b6b6' }, + + progress: { + bg: '#292929', + fg: '#ffd15b', + } } }; \ No newline at end of file diff --git a/src/scss/block/dataview/view/calendar.scss b/src/scss/block/dataview/view/calendar.scss index 4207e150db..01811679d8 100644 --- a/src/scss/block/dataview/view/calendar.scss +++ b/src/scss/block/dataview/view/calendar.scss @@ -30,7 +30,10 @@ .day.active { .number { padding: 0px; color: var(--color-text-inversion); } .number { - .inner { background-color: var(--color-system-accent-100); border-radius: 12px; padding: 0px 8px; align-self: flex-end; min-width: 24px; text-align: center; } + .inner { + background-color: var(--color-system-accent-100); border-radius: 12px; padding: 0px 8px; align-self: flex-end; min-width: 24px; text-align: center; + transition: $transitionAllCommon; + } } } diff --git a/src/scss/component/editor.scss b/src/scss/component/editor.scss index bafeb09294..d3b6f5a21b 100644 --- a/src/scss/component/editor.scss +++ b/src/scss/component/editor.scss @@ -229,10 +229,6 @@ .block.blockDataview { padding: 0px; } .editorControls { height: 52px; width: 100%; margin-bottom: 8px; padding: 0px 14px; } - - .headSimple { - .side.right { display: none; } - } } .block.blockCover { @@ -259,6 +255,12 @@ &.withIconAndCover { .editorControls { display: none; } } + + .blocks { + .headSimple { + .side.right { display: none; } + } + } } .editorWrapper.isSet, diff --git a/src/scss/component/headSimple.scss b/src/scss/component/headSimple.scss index aa445cc06d..1588868da1 100644 --- a/src/scss/component/headSimple.scss +++ b/src/scss/component/headSimple.scss @@ -12,13 +12,13 @@ .editableWrap { flex-grow: 1; } .editableWrap { - .editable { z-index: 1; position: relative; word-break: break-word; cursor: text; } + .editable { z-index: 1; position: relative; word-break: break-word; cursor: text; min-height: 32px; } } > .descr { @include text-paragraph; } } - .side.right { flex-shrink: 0; text-align: right; } + .side.right { text-align: right; display: flex; flex-direction: row; gap: 0px 8px; align-items: center; } .side.right { .button { white-space: nowrap; } } diff --git a/src/scss/form/button.scss b/src/scss/form/button.scss index 6f10fde155..cd78128967 100644 --- a/src/scss/form/button.scss +++ b/src/scss/form/button.scss @@ -37,8 +37,9 @@ .button.dark:not(.disabled).hover { background: rgba(0,0,0,0.4); } .button.blank { background: none; border: solid 1px var(--color-shape-primary); font-weight: 400; } -.button.blank:not(.disabled):hover, -.button.blank:not(.disabled).hover { background: var(--color-shape-highlight-medium); } +.button.blank:not(.disabled) { + &:hover, &.hover, &.active { background: var(--color-shape-highlight-medium); } +} .button.c36 { @include text-common; height: 36px; border-radius: 6px; padding: 0px 12px; } .button.c32 { @include text-small; height: 32px; border-radius: 6px; padding: 0px 10px; } diff --git a/src/scss/list/object.scss b/src/scss/list/object.scss index 46213be0ab..76d5354598 100644 --- a/src/scss/list/object.scss +++ b/src/scss/list/object.scss @@ -1,27 +1,22 @@ @import "~scss/_mixins"; .listObject { - .table { display: grid; border-color: var(--color-shape-secondary); border-style: solid; border-top-width: 1px; margin: 0px 0px 10px 0px; } + .table { display: grid; margin: 0px 0px 10px 0px; } .table { .selectionTarget { display: grid; grid-template-columns: minmax(0, 1fr) 20% 20%; } - .row.isHead { - display: grid; grid-template-columns: minmax(0, 1fr) 20% 20%; color: var(--color-text-secondary); - } + .row.isHead { display: grid; grid-template-columns: minmax(0, 1fr) 20% 20%; color: var(--color-control-active); } .row.isHead { .cell { - text-align: left; padding: 14px 0px 14px 14px; white-space: nowrap; font-weight: 400; line-height: 20px; position: relative; + text-align: left; padding: 9px 0px 9px 14px; white-space: nowrap; font-weight: 400; line-height: 20px; position: relative; @include text-overflow-nw; } .cell { - .name { - display: inline-block; line-height: 20px; height: 20px; vertical-align: top; width: 100%; @include text-overflow-nw; - border-right: 1px solid var(--color-shape-secondary); - } - } - .cell:last-child { - .name { border: 0px; } + .name { display: inline-block; line-height: 20px; height: 20px; vertical-align: top; width: 100%; @include text-overflow-nw; } } + + .icon.sortArrow { width: 20px; height: 20px; margin: 0px; background-image: url('~img/icon/sortArrow.svg'); } + .icon.sortArrow.c1 { transform: rotateZ(180deg); } } .row { border-bottom: 1px solid var(--color-shape-secondary); } @@ -31,7 +26,7 @@ .row.isSelectionSelected { background-color: var(--color-system-selection); } .row { - .cell { padding: 14px; vertical-align: top; position: relative; word-break: break-word; } + .cell { padding: 9px 14px; vertical-align: top; position: relative; word-break: break-word; } .cellContent { width: 100%; overflow: hidden; height: 20px; line-height: 20px; } .cellContent { diff --git a/src/scss/page/common.scss b/src/scss/page/common.scss index 2e85b30323..469079d919 100644 --- a/src/scss/page/common.scss +++ b/src/scss/page/common.scss @@ -16,3 +16,4 @@ @import "./main/onboarding"; @import "./main/chat"; @import "./main/void"; +@import "./main/date"; \ No newline at end of file diff --git a/src/scss/page/main/date.scss b/src/scss/page/main/date.scss new file mode 100644 index 0000000000..91995c6aa7 --- /dev/null +++ b/src/scss/page/main/date.scss @@ -0,0 +1,28 @@ +@import "~scss/_mixins"; + +.pageMainDate { + .wrapper { width: 704px; margin: 0px auto; padding: 40px 0px 80px 0px; user-select: none; } + + .headSimple { align-items: center; height: 32px; } + .headSimple { + .side.right { gap: 0px; } + .side.right { + .icon { width: 24px !important; height: 24px !important; } + .icon.calendar { background-image: url('~img/icon/relation/date.svg'); } + .icon.arrow { background-image: url('~img/arrow/dateSelectLight.svg'); } + .icon.arrow.left { transform: rotateZ(180deg); } + } + } + + .categories { + display: flex; flex-direction: row; gap: 8px; margin: 0px 0px 12px 0px; align-items: center; justify-content: flex-start; flex-wrap: wrap; + } + .categories { + .icon.mention { width: 20px; height: 20px; margin: 0px 6px 0px 0px; background-image: url('~img/icon/mention.svg'); } + .separator { content: ''; background-color: var(--color-shape-secondary); width: 1px; height: 24px; } + } + + .cell.c-type { + .iconObject { display: none; } + } +} \ No newline at end of file diff --git a/src/scss/theme/dark/common.scss b/src/scss/theme/dark/common.scss index 9081614959..abd3b79782 100644 --- a/src/scss/theme/dark/common.scss +++ b/src/scss/theme/dark/common.scss @@ -227,7 +227,7 @@ html.themeDark { /* Progress */ .progress { - .inner { background: var(--color-bg-secondary); } + .inner { background: var(--color-bg-secondary); box-shadow: 0px 4px 16px rgb(0 0 0 / 20%), 0px 0px 0px 1px var(--color-shape-primary) inset; } } .tooltip { color: var(--color-text-primary); } diff --git a/src/ts/component/block/chat.tsx b/src/ts/component/block/chat.tsx index 83a1e3403b..c641c9c07e 100644 --- a/src/ts/component/block/chat.tsx +++ b/src/ts/component/block/chat.tsx @@ -47,6 +47,7 @@ const BlockChat = observer(class BlockChat extends React.Component { - let date = U.Date.dayString(item.createdAt); - if (!date) { - date = U.Date.dateWithFormat(I.DateFormat.MonthAbbrAfterDay, item.createdAt); - }; + const day = showRelativeDates ? U.Date.dayString(item.createdAt) : null; + const date = day ? day : U.Date.dateWithFormat(S.Common.dateFormat, item.createdAt); return (
diff --git a/src/ts/component/block/dataview/cell/text.tsx b/src/ts/component/block/dataview/cell/text.tsx index 4e1c9037f0..cfa154bbf0 100644 --- a/src/ts/component/block/dataview/cell/text.tsx +++ b/src/ts/component/block/dataview/cell/text.tsx @@ -170,8 +170,8 @@ const CellText = observer(class CellText extends React.Component value = Number(value) || 0; const day = showRelativeDates ? U.Date.dayString(value) : null; - const date = day ? day : U.Date.dateWithFormat(viewRelation.dateFormat, value); - const time = U.Date.timeWithFormat(viewRelation.timeFormat, value); + const date = day ? day : U.Date.dateWithFormat(S.Common.dateFormat, value); + const time = U.Date.timeWithFormat(S.Common.timeFormat, value); value = viewRelation.includeTime ? [ date, time ].join((day ? ', ' : ' ')) : date; } else { diff --git a/src/ts/component/block/dataview/view/calendar.tsx b/src/ts/component/block/dataview/view/calendar.tsx index 9e09c80720..17931dacdd 100644 --- a/src/ts/component/block/dataview/view/calendar.tsx +++ b/src/ts/component/block/dataview/view/calendar.tsx @@ -81,14 +81,13 @@ const ViewCalendar = observer(class ViewCalendar extends React.Component {data.map((item, i) => { - const { d, m, y } = item; const cn = []; - const current = [ d, m, y ].join('-'); + const current = [ item.d, item.m, item.y ].join('-'); if (m != item.m) { cn.push('other'); }; - if ((today.d == d) && (today.m == m) && (today.y == y)) { + if ((today.d == item.d) && (today.m == item.m) && (today.y == item.y)) { cn.push('active'); }; if (i < 7) { diff --git a/src/ts/component/block/dataview/view/calendar/item.tsx b/src/ts/component/block/dataview/view/calendar/item.tsx index aa55ce09f9..75b35dc778 100644 --- a/src/ts/component/block/dataview/view/calendar/item.tsx +++ b/src/ts/component/block/dataview/view/calendar/item.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { observer } from 'mobx-react'; import { IconObject, ObjectName } from 'Component'; -import { I, S, U, translate, Preview } from 'Lib'; +import { I, S, U, C, translate, Preview } from 'Lib'; interface Props extends I.ViewComponent { d: number; @@ -21,6 +21,7 @@ const Item = observer(class Item extends React.Component { super(props); this.onOpen = this.onOpen.bind(this); + this.onOpenDate = this.onOpenDate.bind(this); this.onMore = this.onMore.bind(this); this.onContext = this.onContext.bind(this); this.canCreate = this.canCreate.bind(this); @@ -39,6 +40,8 @@ const Item = observer(class Item extends React.Component { cn.push(className); }; + console.log(cn); + let more = null; if (length > LIMIT) { more = ( @@ -73,7 +76,7 @@ const Item = observer(class Item extends React.Component { onContextMenu={this.onContext} onDoubleClick={this.onDoubleClick} > -
+
{d}
@@ -128,16 +131,14 @@ const Item = observer(class Item extends React.Component { onContext () { const node = $(this.node); - const options = []; + const options = [ + { id: 'open', icon: 'expand', name: translate('commonOpenObject') } + ] as I.Option[]; if (this.canCreate()) { options.push({ id: 'add', name: translate('commonNewObject') }); }; - if (!options.length) { - return; - }; - S.Menu.open('select', { element: node, vertical: I.MenuDirection.Bottom, @@ -150,9 +151,10 @@ const Item = observer(class Item extends React.Component { options, noVirtualisation: true, onSelect: (e: any, item: any) => { - if (item.id == 'add') { - this.onCreate(); - } + switch (item.id) { + case 'open': this.onOpenDate(); break; + case 'add': this.onCreate(); break; + }; }, } }); @@ -173,6 +175,12 @@ const Item = observer(class Item extends React.Component { onCreate(details); }; + onOpenDate () { + const { d, m, y } = this.props; + + U.Object.openDateByTimestamp(U.Date.timestamp(y, m, d, 12, 0, 0), 'config'); + }; + canCreate (): boolean { const { getView, isAllowedObject } = this.props; const view = getView(); diff --git a/src/ts/component/footer/main/object.tsx b/src/ts/component/footer/main/object.tsx index 41263f41c0..88da5db8b2 100644 --- a/src/ts/component/footer/main/object.tsx +++ b/src/ts/component/footer/main/object.tsx @@ -2,16 +2,18 @@ import * as React from 'react'; import { observer } from 'mobx-react'; import { PieChart } from 'react-minimal-pie-chart'; import { Icon } from 'Component'; -import { I, S, Preview, translate } from 'Lib'; +import { I, J, S, Preview, translate } from 'Lib'; const FooterMainEdit = observer(class FooterMainEdit extends React.Component { render () { const { onHelp } = this.props; const { show } = S.Progress; + const theme = S.Common.getThemeClass(); const current = S.Progress.getCurrent(); const total = S.Progress.getTotal(); const percent = Math.round((current / total) * 100); + const color = J.Theme[theme].progress; return (
@@ -27,8 +29,8 @@ const FooterMainEdit = observer(class FooterMainEdit extends React.Component
diff --git a/src/ts/component/form/button.tsx b/src/ts/component/form/button.tsx index 835994ecd0..b489cbec9f 100644 --- a/src/ts/component/form/button.tsx +++ b/src/ts/component/form/button.tsx @@ -10,6 +10,7 @@ interface ButtonProps { icon?: string; arrow?: boolean; text?: string; + active?: boolean; color?: string; className?: string; tooltip?: string; @@ -44,7 +45,8 @@ const Button = forwardRef(({ onMouseEnter, onMouseLeave, onMouseDown, - dataset + dataset, + active, }, ref) => { const [ isLoading, setIsLoading ] = useState(false); const nodeRef = useRef(null); @@ -56,6 +58,10 @@ const Button = forwardRef(({ cn.push('isLoading'); }; + if (active) { + cn.push('active'); + }; + const handleMouseEnter = (e: MouseEvent) => { if (tooltip) { Preview.tooltipShow({ text: tooltip, element: $(nodeRef.current), typeX: tooltipX, typeY: tooltipY }); diff --git a/src/ts/component/form/input.tsx b/src/ts/component/form/input.tsx index 8cadd9f427..e6147b897a 100644 --- a/src/ts/component/form/input.tsx +++ b/src/ts/component/form/input.tsx @@ -316,4 +316,4 @@ const Input = forwardRef(({ ); }); -export default Input; +export default Input; \ No newline at end of file diff --git a/src/ts/component/list/object.tsx b/src/ts/component/list/object.tsx index 52821e11ee..23bc60139e 100644 --- a/src/ts/component/list/object.tsx +++ b/src/ts/component/list/object.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { observer } from 'mobx-react'; import { I, C, S, U, J, Relation, translate, keyboard } from 'Lib'; -import { IconObject, Pager, ObjectName, Cell, SelectionTarget } from 'Component'; +import { Icon, IconObject, Pager, ObjectName, Cell, SelectionTarget } from 'Component'; interface Column { relationKey: string; @@ -22,10 +22,15 @@ interface Props { relationKeys?: string[]; }; +interface State { + sortId: string; + sortType: I.SortType; +}; + const PREFIX = 'listObject'; const LIMIT = 50; -const ListObject = observer(class ListObject extends React.Component { +const ListObject = observer(class ListObject extends React.Component { public static defaultProps: Props = { spaceId: '', @@ -36,8 +41,15 @@ const ListObject = observer(class ListObject extends React.Component { filters: [], }; + state = { + sortId: '', + sortType: I.SortType.Desc, + }; + render () { - const { subId, rootId, columns } = this.props; + const { sortId, sortType } = this.state; + const { subId, rootId } = this.props; + const columns = this.getColumns(); const items = this.getItems(); const { offset, total } = S.Record.getMeta(subId, ''); @@ -76,16 +88,8 @@ const ListObject = observer(class ListObject extends React.Component { className={cn.join(' ')} onContextMenu={e => this.onContext(e, item.id)} > -
-
U.Object.openConfig(item)}> -
- - -
-
-
- {columns.map(column => { + const cn = [ 'cell', `c-${column.relationKey}` ]; const cnc = [ 'cellContent' ]; const value = item[column.relationKey]; @@ -98,7 +102,16 @@ const ListObject = observer(class ListObject extends React.Component { if (value) { if (column.isObject) { - const object = S.Detail.get(subId, value, []); + let object = null; + + if (column.relationKey == 'name') { + object = item; + cn.push('isName'); + cnc.push('isName'); + } else { + object = S.Detail.get(subId, value, []); + }; + if (!object._empty_) { onClick = () => U.Object.openConfig(object); content = ( @@ -132,7 +145,7 @@ const ListObject = observer(class ListObject extends React.Component { }; return ( -
+
{content ?
{content}
: ''}
); @@ -145,15 +158,19 @@ const ListObject = observer(class ListObject extends React.Component {
-
-
{translate('commonName')}
-
+ {columns.map(column => { + let arrow = null; - {columns.map(column => ( -
-
{column.name}
-
- ))} + if (sortId == column.relationKey) { + arrow = ; + }; + + return ( +
this.onSort(column.relationKey)}> +
{column.name}{arrow}
+
+ ); + })}
{!items.length ? ( @@ -175,7 +192,11 @@ const ListObject = observer(class ListObject extends React.Component { }; componentDidMount () { - this.getData(1); + const columns = this.getColumns(); + + if (columns.length) { + this.setState({ sortId: columns[0].relationKey }, () => this.getData(1)); + }; }; componentWillUnmount(): void { @@ -190,7 +211,12 @@ const ListObject = observer(class ListObject extends React.Component { return J.Relation.default.concat(this.props.columns.map(it => it.relationKey)); }; + getColumns (): Column[] { + return ([ { relationKey: 'name', name: translate('commonName'), isObject: true } ] as any[]).concat(this.props.columns || []); + }; + getData (page: number, callBack?: (message: any) => void) { + const { sortId, sortType } = this.state; const { spaceId, subId, sources } = this.props; const offset = (page - 1) * LIMIT; const filters = [ @@ -202,9 +228,7 @@ const ListObject = observer(class ListObject extends React.Component { U.Data.searchSubscribe({ spaceId, subId, - sorts: [ - { relationKey: 'lastModifiedDate', type: I.SortType.Desc } - ], + sorts: [ { relationKey: sortId, type: sortType } ], keys: this.getKeys(), sources, filters, @@ -242,6 +266,18 @@ const ListObject = observer(class ListObject extends React.Component { }); }; + onSort (relationKey: string): void { + const { sortId, sortType } = this.state; + + let type = I.SortType.Asc; + + if (sortId == relationKey) { + type = sortType == I.SortType.Asc ? I.SortType.Desc : I.SortType.Asc; + }; + + this.setState({ sortId: relationKey, sortType: type }, () => this.getData(1)); + }; + }); export default ListObject; \ No newline at end of file diff --git a/src/ts/component/menu/block/mention.tsx b/src/ts/component/menu/block/mention.tsx index ed4eaaa091..493c4e9b9d 100644 --- a/src/ts/component/menu/block/mention.tsx +++ b/src/ts/component/menu/block/mention.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { observer } from 'mobx-react'; import $ from 'jquery'; import { MenuItemVertical, Loader, ObjectName, EmptySearch } from 'Component'; -import { I, S, U, J, keyboard, Mark, translate, analytics } from 'Lib'; +import { I, S, U, J, C, keyboard, Mark, translate, analytics } from 'Lib'; import { AutoSizer, CellMeasurer, InfiniteLoader, List, CellMeasurerCache } from 'react-virtualized'; interface State { @@ -31,6 +31,7 @@ const MenuBlockMention = observer(class MenuBlockMention extends React.Component constructor (props: I.Menu) { super(props); + this.rebind = this.rebind.bind(this); this.onClick = this.onClick.bind(this); this.loadMoreRows = this.loadMoreRows.bind(this); }; @@ -67,10 +68,15 @@ const MenuBlockMention = observer(class MenuBlockMention extends React.Component cn.push('isHidden'); }; + let object = null; + if (![ 'add', 'selectDate' ].includes(item.id)) { + object = item; + }; + content = ( } onMouseEnter={e => this.onOver(e, item)} @@ -190,10 +196,30 @@ const MenuBlockMention = observer(class MenuBlockMention extends React.Component const { canAdd } = data; const filter = this.getFilter(); const sections: any[] = []; - const length = this.items.length; + + + let items = U.Common.objectCopy(this.items); + + const dates = items.filter(it => U.Object.isDateLayout(it.layout)); + + items = items.filter(it => !U.Object.isDateLayout(it.layout)); + + const length = items.length; + + if (dates.length) { + sections.push({ + id: 'date', + name: translate('commonDates'), + children: [ + ...dates, + { id: 'selectDate', icon: 'relation c-date', name: translate(`placeholderCell${I.RelationType.Date}`) }, + { isDiv: true }, + ] + }); + }; if (length) { - sections.push({ id: I.MarkType.Object, name: translate('commonObjects'), children: this.items }); + sections.push({ id: I.MarkType.Object, name: translate('commonObjects'), children: items.filter(it => !U.Object.isDateLayout(it.layout)) }); }; if (filter && canAdd) { @@ -301,7 +327,8 @@ const MenuBlockMention = observer(class MenuBlockMention extends React.Component return; }; - const { param, close } = this.props; + const { space } = S.Common; + const { param, getId } = this.props; const { data } = param; const { onChange } = data; const { from } = S.Common.filter; @@ -321,17 +348,43 @@ const MenuBlockMention = observer(class MenuBlockMention extends React.Component onChange(object, name + ' ', marks, from, to + 1); }; + let close = true; + if (item.id == 'add') { const name = this.getFilter(); U.Object.create('', '', { name }, I.BlockPosition.Bottom, '', [ I.ObjectFlag.SelectType, I.ObjectFlag.SelectTemplate ], analytics.route.mention, (message: any) => { cb(message.details); }); + } else + if (item.id == 'selectDate') { + close = false; + + S.Menu.open('dataviewCalendar', { + element: `#${getId()} #item-${item.id}`, + horizontal: I.MenuDirection.Center, + data: { + rebind: this.rebind, + canEdit: true, + value: U.Date.now(), + onChange: (value: number) => { + C.ObjectDateByTimestamp(space, value, (message: any) => { + if (!message.error.code) { + cb(message.details); + this.props.close(); + }; + }); + }, + }, + }); + } else { cb(item); }; - close(); + if (close) { + this.props.close(); + }; }; getRowHeight (item: any) { diff --git a/src/ts/component/menu/block/relation/edit.tsx b/src/ts/component/menu/block/relation/edit.tsx index 0adea01987..8dcae5380c 100644 --- a/src/ts/component/menu/block/relation/edit.tsx +++ b/src/ts/component/menu/block/relation/edit.tsx @@ -93,31 +93,6 @@ const MenuBlockRelationEdit = observer(class MenuBlockRelationEdit extends React
); }; - /* - const opts = ( - - - {isDate && relation ? ( -
-
- -
{translate('menuBlockRelationEditIncludeTime')}
- { this.onChangeTime(v); }} /> -
- - -
- ) : ''} -
- ); - */ return (
render () { const { param } = this.props; const { data, classNameWrap } = param; - const { value, isEmpty, canEdit } = data; + const { value, isEmpty, canEdit, canClear = true } = data; const items = this.getData(); const { m, y } = U.Date.getCalendarDateParam(value); const todayParam = U.Date.getCalendarDateParam(this.originalValue); @@ -94,11 +94,13 @@ const MenuCalendar = observer(class MenuCalendar extends React.Component return (
{ e.stopPropagation(); this.setValue(U.Date.timestamp(item.y, item.m, item.d), true, true); }} + onContextMenu={e => this.onContextMenu(e, item)} > {item.d}
@@ -115,7 +117,7 @@ const MenuCalendar = observer(class MenuCalendar extends React.Component
this.setValue(U.Date.mergeTimeWithDate(tomorrow, value), true, true)}>{translate('commonTomorrow')}
-
this.setValue(null, true, true)}>{translate('commonClear')}
+ {canClear &&
this.setValue(null, true, true)}>{translate('commonClear')}
}
@@ -146,6 +148,29 @@ const MenuCalendar = observer(class MenuCalendar extends React.Component this.props.position(); }; + onContextMenu (e: any, item: any) { + e.preventDefault(); + + const { getId, param } = this.props; + const { className, classNameWrap } = param; + + S.Menu.open('select', { + element: `#${getId()} #${[ 'day', item.d, item.m, item.y ].join('-')}`, + offsetY: 4, + noFlipY: true, + className, + classNameWrap, + data: { + options: [ + { id: 'open', icon: 'expand', name: translate('commonOpenObject') }, + ], + onSelect: () => { + U.Object.openDateByTimestamp(U.Date.timestamp(item.y, item.m, item.d)); + } + } + }); + }; + setValue (value: number, save: boolean, close: boolean) { const { param, id } = this.props; const { data } = param; diff --git a/src/ts/component/menu/dataview/relation/edit.tsx b/src/ts/component/menu/dataview/relation/edit.tsx index 4b4c4c160c..0305928764 100644 --- a/src/ts/component/menu/dataview/relation/edit.tsx +++ b/src/ts/component/menu/dataview/relation/edit.tsx @@ -17,7 +17,6 @@ const MenuRelationEdit = observer(class MenuRelationEdit extends React.Component this.onRelationType = this.onRelationType.bind(this); this.onObjectType = this.onObjectType.bind(this); - this.onDateSettings = this.onDateSettings.bind(this); this.onSubmit = this.onSubmit.bind(this); this.onChange = this.onChange.bind(this); this.onClick = this.onClick.bind(this); @@ -84,15 +83,6 @@ const MenuRelationEdit = observer(class MenuRelationEdit extends React.Component switchValue={viewRelation?.includeTime} onSwitch={(e: any, v: boolean) => { this.onChangeTime(v); }} /> - -
); }; @@ -595,32 +585,6 @@ const MenuRelationEdit = observer(class MenuRelationEdit extends React.Component }); }; - onDateSettings (e: any) { - e.preventDefault(); - e.stopPropagation(); - - const { param, getId } = this.props; - const { data } = param; - const { readonly } = data; - - if (readonly) { - return; - }; - - const relation = this.getRelation(); - - this.menuOpen('dataviewDate', { - element: `#${getId()} #item-date-settings`, - onClose: () => { - S.Menu.close('select'); - }, - data: { - ...data, - relationKey: relation.relationKey, - } - }); - }; - menuOpen (id: string, options: I.MenuParam) { const { getSize, param } = this.props; const { classNameWrap } = param; diff --git a/src/ts/component/menu/object.tsx b/src/ts/component/menu/object.tsx index 3edfdd2d03..f56345005c 100644 --- a/src/ts/component/menu/object.tsx +++ b/src/ts/component/menu/object.tsx @@ -94,6 +94,14 @@ class MenuObject extends React.Component { const object = this.getObject(); const cmd = keyboard.cmdSymbol(); const isTemplate = U.Object.isTemplate(object.type); + const isDate = U.Object.isDateLayout(object.layout); + const isChat = U.Object.isChatLayout(object.layout); + const isBookmark = U.Object.isBookmarkLayout(object.layout); + const isParticipant = U.Object.isParticipantLayout(object.layout); + const isInSetLayouts = U.Object.isInSetLayouts(object.layout); + const isInFileLayouts = U.Object.isInFileLayouts(object.layout); + const isInFileOrSystemLayouts = U.Object.isInFileOrSystemLayouts(object.layout); + const isTypeOrRelationLayout = U.Object.isTypeOrRelationLayout(object.layout); const canWrite = U.Space.canMyParticipantWrite(); const canDelete = S.Block.checkFlags(rootId, rootId, [ I.RestrictionObject.Delete ]); @@ -157,31 +165,31 @@ class MenuObject extends React.Component { // Restrictions const hasShortMenu = ( - U.Object.isTypeOrRelationLayout(object.layout) || - U.Object.isInFileLayouts(object.layout) || - U.Object.isInSetLayouts(object.layout) || - U.Object.isParticipantLayout(object.layout) || - U.Object.isChatLayout(object.layout) + isTypeOrRelationLayout || + isInFileLayouts || + isInSetLayouts || + isParticipant || + isChat ); const allowedArchive = canWrite && canDelete; - const allowedSearch = !isFilePreview && !U.Object.isInSetLayouts(object.layout); - const allowedHistory = !object.isArchived && !U.Object.isInFileOrSystemLayouts(object.layout) && !U.Object.isParticipantLayout(object.layout) && !object.templateIsBundled; + const allowedSearch = !isFilePreview && !isInSetLayouts; + const allowedHistory = !object.isArchived && !isInFileOrSystemLayouts && !isParticipant && !isDate && !object.templateIsBundled; const allowedFav = canWrite && !object.isArchived && !object.templateIsBundled; const allowedLock = canWrite && !object.isArchived && S.Block.checkFlags(rootId, rootId, [ I.RestrictionObject.Details ]); const allowedLinkTo = canWrite && !object.isArchived; const allowedAddCollection = canWrite && !object.isArchived; const allowedPageLink = !object.isArchived; const allowedCopy = canWrite && !object.isArchived && S.Block.checkFlags(rootId, rootId, [ I.RestrictionObject.Duplicate ]); - const allowedReload = canWrite && object.source && U.Object.isBookmarkLayout(object.layout); - const allowedInstall = canWrite && !object.isInstalled && U.Object.isTypeOrRelationLayout(object.layout); - const allowedUninstall = canWrite && object.isInstalled && U.Object.isTypeOrRelationLayout(object.layout) && canDelete; + const allowedReload = canWrite && object.source && isBookmark; + const allowedInstall = canWrite && !object.isInstalled && isTypeOrRelationLayout; + const allowedUninstall = canWrite && object.isInstalled && isTypeOrRelationLayout && canDelete; const allowedTemplate = canWrite && !U.Object.getLayoutsWithoutTemplates().includes(object.layout) && S.Block.checkFlags(rootId, rootId, [ I.RestrictionObject.Template ]); const allowedWidget = canWrite && !object.isArchived && !S.Block.checkBlockTypeExists(rootId); - const allowedExport = !isFilePreview && !U.Object.isChatLayout(object.layout); + const allowedExport = !isFilePreview && !isChat && !isDate; const allowedPrint = !isFilePreview; - const allowedDownloadFile = U.Object.isInFileLayouts(object.layout); - const allowedOpenFile = U.Object.isInFileLayouts(object.layout); + const allowedDownloadFile = isInFileLayouts; + const allowedOpenFile = isInFileLayouts; const allowedOpenObject = isFilePreview; if (!allowedArchive) archive = null; diff --git a/src/ts/component/page/elements/head/simple.tsx b/src/ts/component/page/elements/head/simple.tsx index d35382dc09..efae3604aa 100644 --- a/src/ts/component/page/elements/head/simple.tsx +++ b/src/ts/component/page/elements/head/simple.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { observer } from 'mobx-react'; -import { IconObject, Block, Button, Editable } from 'Component'; -import { I, M, S, U, J, Action, focus, keyboard, Relation, translate } from 'Lib'; +import { IconObject, Block, Button, Editable, Icon } from 'Component'; +import { I, M, S, U, J, Action, focus, keyboard, Relation, translate, C } from 'Lib'; interface Props { rootId: string; @@ -44,9 +44,11 @@ const HeadSimple = observer(class Controls extends React.Component { const blockFeatured: any = new M.Block({ id: 'featuredRelations', type: I.BlockType.Featured, childrenIds: [], fields: {}, content: {} }); const isTypeOrRelation = U.Object.isTypeOrRelationLayout(object.layout); + const isDate = U.Object.isDateLayout(object.layout); const isRelation = U.Object.isRelationLayout(object.layout); const canEditIcon = allowDetails && !U.Object.isRelationLayout(object.layout); const cn = [ 'headSimple', check.className ]; + const placeholder = { title: this.props.placeholder, description: translate('placeholderBlockDescription'), @@ -70,11 +72,11 @@ const HeadSimple = observer(class Controls extends React.Component { /> ); - let button = null; + let button: React.ReactElement = null; let descr = null; let featured = null; - if (!isTypeOrRelation) { + if (!isTypeOrRelation && !isDate) { if (featuredRelations.includes('description')) { descr = ; }; @@ -114,6 +116,16 @@ const HeadSimple = observer(class Controls extends React.Component { }; }; + if (isDate) { + button = ( + + this.changeDate(-1)} /> + this.changeDate(1)}/> + + + ); + }; + if (!canWrite) { button = null; }; @@ -221,6 +233,7 @@ const HeadSimple = observer(class Controls extends React.Component { }; setValue () { + const { dateFormat } = S.Common; const { rootId } = this.props; const object = S.Detail.get(rootId, rootId); @@ -230,6 +243,11 @@ const HeadSimple = observer(class Controls extends React.Component { }; let text = String(object[item.relationKey] || ''); + + if (U.Object.isDateLayout(object.layout) && object.timestamp) { + text = U.Date.dateWithFormat(dateFormat, object.timestamp); + }; + if (text == translate('defaultNamePage')) { text = ''; }; @@ -285,6 +303,29 @@ const HeadSimple = observer(class Controls extends React.Component { return sources.includes(rootId); }; + onCalendar = () => { + const { rootId } = this.props; + const object = S.Detail.get(rootId, rootId); + + S.Menu.open('dataviewCalendar', { + element: '#calendar-icon', + horizontal: I.MenuDirection.Center, + data: { + value: object.timestamp, + canEdit: true, + canClear: false, + onChange: (value: number) => U.Object.openDateByTimestamp(value), + }, + }); + }; + + changeDate = (dir: number) => { + const { rootId } = this.props; + const object = S.Detail.get(rootId, rootId); + + U.Object.openDateByTimestamp(object.timestamp + dir * 86400); + }; + }); export default HeadSimple; \ No newline at end of file diff --git a/src/ts/component/page/index.tsx b/src/ts/component/page/index.tsx index 24d16bdb3c..7ed6edabe5 100644 --- a/src/ts/component/page/index.tsx +++ b/src/ts/component/page/index.tsx @@ -31,6 +31,7 @@ import PageMainMembership from './main/membership'; import PageMainObject from './main/object'; import PageMainOnboarding from './main/onboarding'; import PageMainChat from './main/chat'; +import PageMainDate from './main/date'; const Components = { 'index/index': PageAuthSelect, @@ -61,6 +62,7 @@ const Components = { 'main/onboarding': PageMainOnboarding, 'main/chat': PageMainChat, 'main/void': PageMainVoid, + 'main/date': PageMainDate, }; const Page = observer(class Page extends React.Component { diff --git a/src/ts/component/page/main/date.tsx b/src/ts/component/page/main/date.tsx new file mode 100644 index 0000000000..5b8260964d --- /dev/null +++ b/src/ts/component/page/main/date.tsx @@ -0,0 +1,237 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; +import { Header, Footer, Deleted, ListObject, Button } from 'Component'; +import { I, C, S, U, Action, translate } from 'Lib'; +import HeadSimple from 'Component/page/elements/head/simple'; + +interface State { + isDeleted: boolean; + relations: any[]; + selectedRelation: string; +}; + +const SUB_ID = 'dateListObject'; +const RELATION_KEY_MENTION = 'mentions'; + +const PageMainDate = observer(class PageMainDate extends React.Component { + + _isMounted = false; + node: any = null; + id = ''; + refHeader: any = null; + refHead: any = null; + refList: any = null; + refCalIcon: any = null; + loading = false; + timeout = 0; + + state = { + isDeleted: false, + relations: [], + selectedRelation: RELATION_KEY_MENTION, + }; + + render () { + const { space } = S.Common; + const { isDeleted, relations, selectedRelation } = this.state; + const rootId = this.getRootId(); + const object = S.Detail.get(rootId, rootId, []); + + if (isDeleted) { + return ; + }; + + const relation = S.Record.getRelationByKey(selectedRelation); + const columns: any[] = [ + { relationKey: 'type', name: translate('commonObjectType'), isObject: true }, + { relationKey: 'creator', name: translate('relationCreator'), isObject: true }, + ]; + + const filters: I.Filter[] = []; + + if (relation.format == I.RelationType.Object) { + filters.push({ relationKey: RELATION_KEY_MENTION, condition: I.FilterCondition.In, value: [ object.id ] }); + } else { + filters.push({ relationKey: selectedRelation, condition: I.FilterCondition.Equal, value: object.timestamp, format: I.RelationType.Date }); + }; + + return ( +
this.node = node}> +
this.refHeader = ref} + rootId={rootId} + /> + +
+ this.refHead = ref} + rootId={rootId} + readonly={true} + /> + +
+ {relations.map((item) => { + const isMention = item.relationKey == RELATION_KEY_MENTION; + const icon = isMention ? 'mention' : ''; + const separator = isMention ?
: ''; + + return ( + +
+ +
+ this.refList = ref} + {...this.props} + spaceId={space} + subId={SUB_ID} + rootId={rootId} + columns={columns} + filters={filters} + /> +
+
+ +
+
+ ); + }; + + componentDidMount () { + this._isMounted = true; + this.open(); + }; + + componentDidUpdate () { + this.open(); + this.checkDeleted(); + }; + + componentWillUnmount () { + this._isMounted = false; + this.close(); + }; + + checkDeleted () { + const { isDeleted } = this.state; + if (isDeleted) { + return; + }; + + const rootId = this.getRootId(); + const object = S.Detail.get(rootId, rootId, []); + + if (object.isDeleted) { + this.setState({ isDeleted: true }); + }; + }; + + open () { + const rootId = this.getRootId(); + + if (this.id == rootId) { + return; + }; + + this.close(); + this.id = rootId; + this.setState({ isDeleted: false }); + + C.ObjectOpen(rootId, '', U.Router.getRouteSpaceId(), (message: any) => { + if (!U.Common.checkErrorOnOpen(rootId, message.error.code, this)) { + return; + }; + + const object = S.Detail.get(rootId, rootId, []); + if (object.isDeleted) { + this.setState({ isDeleted: true }); + return; + }; + + this.refHeader?.forceUpdate(); + this.refHead?.forceUpdate(); + + this.loadCategory(); + }); + }; + + close () { + if (!this.id) { + return; + }; + + const { isPopup, match } = this.props; + + let close = true; + if (isPopup && (match.params.id == this.id)) { + close = false; + }; + if (close) { + Action.pageClose(this.id, true); + }; + }; + + loadCategory () { + const { space, config } = S.Common; + const rootId = this.getRootId(); + + C.RelationListWithValue(space, rootId, (message: any) => { + const relations = (message.relations || []).map(it => S.Record.getRelationByKey(it.relationKey)).filter(it => { + if ([ RELATION_KEY_MENTION ].includes(it.relationKey)) { + return true; + }; + + if ([ 'links', 'backlinks' ].includes(it.relationKey)) { + return false; + }; + + return config.debug.hidden ? true : !it.isHidden; + }); + + relations.sort((c1, c2) => { + const isMention1 = c1.relationKey == RELATION_KEY_MENTION; + const isMention2 = c2.relationKey == RELATION_KEY_MENTION; + + if (isMention1 && !isMention2) return -1; + if (!isMention1 && isMention2) return 1; + return 0; + }); + + if (relations.length) { + this.setState({ relations }); + this.onCategory(relations[0].relationKey); + }; + }); + }; + + onCategory (relationKey: string) { + this.setState({ selectedRelation: relationKey }, () => { + this.refList?.getData(1); + }); + }; + + getRootId () { + const { rootId, match } = this.props; + return rootId ? rootId : match.params.id; + }; + +}); + +export default PageMainDate; diff --git a/src/ts/component/page/main/relation.tsx b/src/ts/component/page/main/relation.tsx index 6c6532beac..85cde5c342 100644 --- a/src/ts/component/page/main/relation.tsx +++ b/src/ts/component/page/main/relation.tsx @@ -44,7 +44,7 @@ const PageMainRelation = observer(class PageMainRelation extends React.Component const columnsObject: any[] = [ { relationKey: 'lastModifiedDate', name: translate('commonUpdated'), - mapper: v => U.Date.dateWithFormat(I.DateFormat.MonthAbbrBeforeDay, v), + mapper: v => U.Date.dateWithFormat(S.Common.dateFormat, v), }, { relationKey: object.relationKey, name: object.name, isCell: true } ]; diff --git a/src/ts/component/page/main/type.tsx b/src/ts/component/page/main/type.tsx index 75ff3b2dc8..c3849dbeb8 100644 --- a/src/ts/component/page/main/type.tsx +++ b/src/ts/component/page/main/type.tsx @@ -92,7 +92,7 @@ const PageMainType = observer(class PageMainType extends React.Component v ? U.Date.dateWithFormat(I.DateFormat.MonthAbbrBeforeDay, v) : '', + mapper: v => v ? U.Date.dateWithFormat(S.Common.dateFormat, v) : '', }, ]; diff --git a/src/ts/component/popup/page/settings/personal.tsx b/src/ts/component/popup/page/settings/personal.tsx index e2e44be17f..38c664a841 100644 --- a/src/ts/component/popup/page/settings/personal.tsx +++ b/src/ts/component/popup/page/settings/personal.tsx @@ -7,7 +7,7 @@ const PopupSettingsPagePersonal = observer(class PopupSettingsPagePersonal exten render () { const { getId } = this.props; - const { config, interfaceLang, navigationMenu, linkStyle, fullscreenObject, hideSidebar, showRelativeDates, showVault } = S.Common; + const { config, interfaceLang, navigationMenu, linkStyle, fullscreenObject, hideSidebar, showRelativeDates, showVault, dateFormat, timeFormat, } = S.Common; const { hideTray, hideMenuBar, languages } = config; const canHideMenu = U.Common.isPlatformWindows() || U.Common.isPlatformLinux(); @@ -105,17 +105,6 @@ const PopupSettingsPagePersonal = observer(class PopupSettingsPagePersonal exten />
-
-