+
@@ -173,6 +176,18 @@ const Item = observer(class Item extends React.Component
{
onCreate(details);
};
+ onOpenDate () {
+ const { space } = S.Common;
+ const { d, m, y } = this.props;
+ const ts = U.Date.timestamp(y, m, d, 12, 0, 0);
+
+ C.ObjectDateByTimestamp(space, ts, (message: any) => {
+ if (!message.error.code) {
+ U.Object.openConfig(message.details);
+ };
+ });
+ };
+
canCreate (): boolean {
const { getView, isAllowedObject } = this.props;
const view = getView();
diff --git a/src/ts/component/form/button.tsx b/src/ts/component/form/button.tsx
index 3ea06022f8..2e56b1afa7 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 => (
-
- ))}
+ 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/dataview/calendar.tsx b/src/ts/component/menu/dataview/calendar.tsx
index 4ddb9d6e42..11cdf533ce 100644
--- a/src/ts/component/menu/dataview/calendar.tsx
+++ b/src/ts/component/menu/dataview/calendar.tsx
@@ -12,7 +12,7 @@ const MenuCalendar = observer(class MenuCalendar extends React.Component
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);
@@ -115,7 +115,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')}
}
diff --git a/src/ts/component/page/elements/head/simple.tsx b/src/ts/component/page/elements/head/simple.tsx
index d35382dc09..6fd516eca6 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;
};
@@ -285,6 +297,35 @@ const HeadSimple = observer(class Controls extends React.Component {
return sources.includes(rootId);
};
+ onCalendar = () => {
+ const { rootId } = this.props;
+ const { space } = S.Common;
+ const object = S.Detail.get(rootId, rootId, [ 'timestamp' ]);
+
+ S.Menu.open('dataviewCalendar', {
+ element: '#calendar-icon',
+ horizontal: I.MenuDirection.Center,
+ data: {
+ value: object.timestamp,
+ canEdit: true,
+ canClear: false,
+ onChange: (value: number) => {
+ C.ObjectDateByTimestamp(space, value, (message: any) => {
+ U.Object.openAuto(message.details);
+ });
+ },
+ },
+ });
+ };
+
+ changeDate = (dir: number) => {
+ const { rootId } = this.props;
+ const object = S.Detail.get(rootId, rootId, ['timestamp']);
+ C.ObjectDateByTimestamp(U.Router.getRouteSpaceId(), object.timestamp + dir * 24 * 60 * 60, (message: any) => {
+ U.Object.openAuto(message.details);
+ });
+ };
+
});
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..a653ea6c68
--- /dev/null
+++ b/src/ts/component/page/main/date.tsx
@@ -0,0 +1,236 @@
+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, [ 'timestamp' ]);
+
+ if (isDeleted) {
+ return ;
+ };
+
+ const columns: any[] = [
+ { relationKey: 'type', name: translate('commonObjectType'), isObject: true },
+ { relationKey: 'creator', name: translate('relationCreator'), isObject: true },
+ ];
+
+ const filters: I.Filter[] = [];
+
+ if (selectedRelation == RELATION_KEY_MENTION) {
+ 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
/>
-
-
- {
- S.Common.showRelativeDatesSet(v);
- analytics.event('RelativeDates', { type: v });
- }}
- />
-
+
+
+
+ {
+ S.Common.showRelativeDatesSet(v);
+ analytics.event('RelativeDates', { type: v });
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/src/ts/component/popup/page/settings/space/index.tsx b/src/ts/component/popup/page/settings/space/index.tsx
index ee543c7ecd..e011ad5a71 100644
--- a/src/ts/component/popup/page/settings/space/index.tsx
+++ b/src/ts/component/popup/page/settings/space/index.tsx
@@ -408,7 +408,7 @@ const PopupSettingsSpaceIndex = observer(class PopupSettingsSpaceIndex extends R