diff --git a/src/json/relation.ts b/src/json/relation.ts index 4e93a9350c..5bef864c90 100644 --- a/src/json/relation.ts +++ b/src/json/relation.ts @@ -22,7 +22,8 @@ export default { 'sizeInBytes', 'restrictions', 'defaultTemplateId', - 'createdDate' + 'createdDate', + 'relationOptionColor', ], sidebar: [ diff --git a/src/json/text.json b/src/json/text.json index ead7f758fa..d830fa375e 100644 --- a/src/json/text.json +++ b/src/json/text.json @@ -170,6 +170,8 @@ "commonSystem": "System", "commonMainChat": "Main сhat", "commonMenu": "Menu", + "commonDateCreated": "Date created", + "commonDateUpdated": "Date updated", "pluralDay": "day|days", "pluralObject": "Object|Objects", @@ -474,6 +476,8 @@ "pageMainVoidText": "Looks like you’ve cleaned the house. Ready to start fresh?
Create a new space to get things rolling!", "pageMainVoidCreateSpace": "Create space", + "pageMainTagTagColor": "Tag color", + "pageAuthLoginInvalidPhrase": "Invalid Key", "pageAuthLoginShortPhrase": "Key is too short", @@ -1697,6 +1701,7 @@ "defaultNameRelation": "New Relation", "defaultNameBookmark": "Paste Link", "defaultNameView": "New View", + "defaultNameTag": "New Tag", "placeholderBlock": "Type text or \/ for commands", "placeholderBlockDescription": "Add a description", @@ -1816,8 +1821,6 @@ "sidebarObjectOrphanLabel": "Unlinked objects that do not have a direct link or backlink with other objects in the graph.", "sidebarObjectEmpty": "It’s empty here.
Create your first objects to get started.", - "sidebarObjectSortCreated": "Date created", - "sidebarObjectSortUpdated": "Date updated", "sidebarObjectSortLastUsed": "Date last used", "unsplashString": "Photo by %s on %s", diff --git a/src/scss/component/headSimple.scss b/src/scss/component/headSimple.scss index aa445cc06d..3d9a13eca7 100644 --- a/src/scss/component/headSimple.scss +++ b/src/scss/component/headSimple.scss @@ -36,4 +36,12 @@ .headSimple.isType, .headSimple.isRelation { .side.left { width: 32px !important; padding-top: 2px; } .side.center { line-height: 36px; } -} \ No newline at end of file +} + +.headSimple.withColorPicker { + .side.left { + .titleWrap { justify-content: space-between; align-items: center; } + .editableWrap { flex-grow: 0; font-weight: 400; padding: 0px 6px; border-radius: 8px; } + .colorPicker { width: 18px; height: 18px; border-radius: 50%; } + } +} diff --git a/src/scss/form/editable.scss b/src/scss/form/editable.scss index 8e841eb837..501efd3ab6 100644 --- a/src/scss/form/editable.scss +++ b/src/scss/form/editable.scss @@ -2,4 +2,4 @@ .editableWrap { .editable { -webkit-user-modify: read-write-plaintext-only; } -} \ No newline at end of file +} diff --git a/src/scss/list/object.scss b/src/scss/list/object.scss index 46213be0ab..9edbb8a30c 100644 --- a/src/scss/list/object.scss +++ b/src/scss/list/object.scss @@ -3,10 +3,10 @@ .listObject { .table { display: grid; border-color: var(--color-shape-secondary); border-style: solid; border-top-width: 1px; margin: 0px 0px 10px 0px; } .table { - .selectionTarget { display: grid; grid-template-columns: minmax(0, 1fr) 20% 20%; } + .selectionTarget { display: grid; grid-template-columns: minmax(0, 1fr) 20% 20% 20%; } .row.isHead { - display: grid; grid-template-columns: minmax(0, 1fr) 20% 20%; color: var(--color-text-secondary); + display: grid; grid-template-columns: minmax(0, 1fr) 20% 20% 20%; color: var(--color-text-secondary); } .row.isHead { .cell { @@ -47,4 +47,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/scss/menu/dataview/option.scss b/src/scss/menu/dataview/option.scss index 11db72a202..6534e7629c 100644 --- a/src/scss/menu/dataview/option.scss +++ b/src/scss/menu/dataview/option.scss @@ -60,6 +60,7 @@ .menu.menuDataviewOptionEdit { .content { padding-top: 12px; overflow: visible; max-height: unset; } .filter { margin-bottom: 8px; } + .noRemove { padding-bottom: 16px; } .item:hover::before { background: var(--color-shape-highlight-medium); } .item.active::before { z-index: 1; } diff --git a/src/scss/page/common.scss b/src/scss/page/common.scss index 2e85b30323..23f6e021be 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/tag"; diff --git a/src/scss/page/main/tag.scss b/src/scss/page/main/tag.scss new file mode 100644 index 0000000000..383ad75d46 --- /dev/null +++ b/src/scss/page/main/tag.scss @@ -0,0 +1,27 @@ +@import "~scss/_mixins"; + +.pageMainTag { user-select: none; } +.pageMainTag { + .wrapper { width: 704px; margin: 0px auto; padding: 74px 0px 80px 0px; } + .wrapper { + .headSimple { margin-bottom: 52px; } + + .block { + .wrapMenu { display: none; } + .wrapContent { width: 100%; } + } + + .section { + .title { @include text-header2; margin-bottom: 14px; } + } + + .listObject { + .table { + .row, + .row.isHead { + .cell:first-child { padding-left: 0px; } + } + } + } + } +} diff --git a/src/ts/component/editor/page.tsx b/src/ts/component/editor/page.tsx index e0a315bb3c..4929963f13 100644 --- a/src/ts/component/editor/page.tsx +++ b/src/ts/component/editor/page.tsx @@ -2365,4 +2365,4 @@ const EditorPage = observer(class EditorPage extends React.Component { }; placeholderShow () { - $(this.refPlaceholder).show(); + const h = parseInt($(this.refEditable).css('line-height')); + + $(this.refPlaceholder).css({ marginTop: -h, position: 'relative' }).show(); }; setValue (html: string) { diff --git a/src/ts/component/menu/dataview/option/edit.tsx b/src/ts/component/menu/dataview/option/edit.tsx index 0296cbb4cf..8650c2d048 100644 --- a/src/ts/component/menu/dataview/option/edit.tsx +++ b/src/ts/component/menu/dataview/option/edit.tsx @@ -14,7 +14,7 @@ const MenuOptionEdit = observer(class MenuOptionEdit extends React.Component ( @@ -50,15 +50,17 @@ const MenuOptionEdit = observer(class MenuOptionEdit extends React.Component - this.refName = ref} - placeholder={translate('menuDataviewOptionEditPlaceholder')} - placeholderFocus={translate('menuDataviewOptionEditPlaceholder')} - className={'outlined textColor-' + this.color} - value={option.name} - onKeyUp={(e: any, v: string) => { this.onKeyUp(e, v); }} - /> +
+ {!noFilter ? ( + this.refName = ref} + placeholder={translate('menuDataviewOptionEditPlaceholder')} + placeholderFocus={translate('menuDataviewOptionEditPlaceholder')} + className={'outlined textColor-' + this.color} + value={option.name} + onKeyUp={(e: any, v: string) => { this.onKeyUp(e, v); }} + /> + ) : ''} {sections.map((item: any, i: number) => (
@@ -104,16 +106,22 @@ const MenuOptionEdit = observer(class MenuOptionEdit extends React.Component it.id != 'bgColor-default'); + const sections = [ { children: colors, className: 'colorPicker' } ]; - return [ - { children: colors, className: 'colorPicker' }, - { + if (!noRemove) { + sections.push({ + className: '', children: [ { id: 'remove', icon: 'remove', name: translate('menuDataviewOptionEditDelete') } - ] - }, - ]; + ] + }); + }; + + return sections; }; getItems () { @@ -200,9 +208,14 @@ const MenuOptionEdit = observer(class MenuOptionEdit extends React.Component void; + onColorChange?: (color: string) => void; }; const EDITORS = [ @@ -32,21 +35,24 @@ const HeadSimple = observer(class Controls extends React.Component { this.onInstall = this.onInstall.bind(this); this.onCompositionStart = this.onCompositionStart.bind(this); + this.onColorPicker = this.onColorPicker.bind(this); }; render (): any { - const { rootId, onCreate, isContextMenuDisabled, readonly, noIcon } = this.props; + const { rootId, onCreate, isContextMenuDisabled, readonly, noIcon, withColorPicker } = this.props; const check = U.Data.checkDetails(rootId); const object = S.Detail.get(rootId, rootId, [ 'featuredRelations' ]); const featuredRelations = Relation.getArrayValue(object.featuredRelations); const allowDetails = !readonly && S.Block.checkFlags(rootId, rootId, [ I.RestrictionObject.Details ]); const canWrite = U.Space.canMyParticipantWrite(); + const theme = S.Common.getThemeClass(); const blockFeatured: any = new M.Block({ id: 'featuredRelations', type: I.BlockType.Featured, childrenIds: [], fields: {}, content: {} }); const isTypeOrRelation = U.Object.isTypeOrRelationLayout(object.layout); const isRelation = U.Object.isRelationLayout(object.layout); const canEditIcon = allowDetails && !U.Object.isRelationLayout(object.layout); const cn = [ 'headSimple', check.className ]; + const titleCn = [ 'title' ]; const placeholder = { title: this.props.placeholder, description: translate('placeholderBlockDescription'), @@ -118,6 +124,12 @@ const HeadSimple = observer(class Controls extends React.Component { button = null; }; + if (withColorPicker) { + cn.push('withColorPicker'); + titleCn.push(`isMultiSelect`); + titleCn.push(`tagColor-${object.color || 'default'}`); + }; + return (
this.node = node} className={cn.join(' ')}>
@@ -131,7 +143,16 @@ const HeadSimple = observer(class Controls extends React.Component { canEdit={canEditIcon} /> ) : ''} - + + + {withColorPicker ? ( +
+ ) : ''}
{descr} @@ -285,6 +306,27 @@ const HeadSimple = observer(class Controls extends React.Component { return sources.includes(rootId); }; + onColorPicker () { + const { rootId, onColorChange, colorPickerTitle } = this.props; + const object = S.Detail.get(rootId, rootId); + + S.Menu.open('dataviewOptionEdit', { + element: `#colorPicker`, + offsetY: 4, + title: colorPickerTitle || translate('commonColor'), + data: { + option: { color: object.color }, + onColorPick: (color) => { + if (onColorChange) { + onColorChange(color); + }; + }, + noFilter: true, + noRemove: true + } + }); + }; + }); -export default HeadSimple; \ No newline at end of file +export default HeadSimple; diff --git a/src/ts/component/page/index.tsx b/src/ts/component/page/index.tsx index 24d16bdb3c..6e2fef6079 100644 --- a/src/ts/component/page/index.tsx +++ b/src/ts/component/page/index.tsx @@ -31,6 +31,9 @@ import PageMainMembership from './main/membership'; import PageMainObject from './main/object'; import PageMainOnboarding from './main/onboarding'; import PageMainChat from './main/chat'; +import PageMainTag from './main/tag'; +import PageMainDate from './main/date'; +import { createRef } from 'react'; const Components = { 'index/index': PageAuthSelect, @@ -61,12 +64,12 @@ const Components = { 'main/onboarding': PageMainOnboarding, 'main/chat': PageMainChat, 'main/void': PageMainVoid, -}; - + 'main/date': PageMainDate, +} as const; const Page = observer(class Page extends React.Component { _isMounted = false; - refChild: any = null; + refChild: React.RefObject = createRef(); frame = 0; render () { @@ -98,7 +101,7 @@ const Page = observer(class Page extends React.Component { const wrap = (
- this.refChild = ref} {...this.props} /> +
); @@ -353,8 +356,8 @@ const Page = observer(class Page extends React.Component { return; }; - if (this.refChild && this.refChild.resize) { - this.refChild.resize(); + if (this.refChild.current && this.refChild.current.resize) { + this.refChild.current.resize(); }; sidebar.resizePage(null, false); diff --git a/src/ts/component/page/main/date/index.tsx b/src/ts/component/page/main/date/index.tsx new file mode 100644 index 0000000000..3a8f17dcae --- /dev/null +++ b/src/ts/component/page/main/date/index.tsx @@ -0,0 +1,333 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; +import { Action, C, I, J, Renderer, S, translate, U } from 'Lib'; +import { Header, Footer, ListObject, Button } from 'Component'; +import HeadSimple from 'Component/page/elements/head/simple'; +import RelationsSelector from './relations'; +import { useParams } from 'react-router-dom'; +import { useEffect, useRef, useState } from 'react'; +import { set } from 'lodash'; +import { number, unknown } from '@rspack/core/compiled/zod'; +import { then } from 'Lib/util/then'; + +interface State { + isLoading: boolean; + isDeleted: boolean; + relationKeyWithCounterList: { relationKey: string, counter: number }[]; +}; + +// const PageMainDate = observer(class PageMainDate extends React.Component { + +// _isMounted = false; +// node: any = null; +// id = ''; +// refHeader: any = null; +// refHead: any = null; + +// state = { +// isLoading: false, +// isDeleted: false, +// relationKeyWithCounterList: [], +// }; + +// render() { +// const rootId = this.getRootId(); +// const object = S.Detail.get(rootId, rootId, ['backlinks']); + +// const subId = this.getSubId(); +// const total = S.Record.getMeta(subId, '').total; + +// const filters: I.Filter[] = [{ relationKey: 'id', condition: I.FilterCondition.In, value: object.backlinks || [] }]; + +// const columns: any[] = [ +// { relationKey: 'type', name: translate('commonObjectType'), isObject: true }, +// { relationKey: 'creator', name: translate('commonOwner'), isObject: true }, +// ]; + +// return ( +//
this.node = node}> +//
+// this.refHead = ref} +// placeholder={translate('defaultNameTag')} +// rootId={rootId} +// isContextMenuDisabled={true} +// noIcon={true} +// withColorPicker={false} +// /> +// +// {!object._empty_ ? ( +//
+//
{total} {U.Common.plural(total, translate('pluralObject'))}
+//
+// +//
+//
+// ) : ''} +//
+ +//
+//
+// ); +// }; + +// componentDidMount() { +// this._isMounted = true; +// this.open(); +// }; + +// componentDidUpdate() { +// this.open(); +// }; + +// componentWillUnmount() { +// this._isMounted = false; +// this.close(); +// }; + +// open() { +// const rootId = this.getRootId(); + +// if (this.id == rootId) { +// return; +// }; + +// this.close(); +// this.id = rootId; +// this.setState({ isDeleted: false, isLoading: true }); + +// 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, isLoading: false }); +// return; +// }; + +// this.refHeader?.forceUpdate(); +// this.refHead?.forceUpdate(); +// this.setState({ isLoading: false }); +// }); +// }; + +// 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); +// }; +// }; + +// getSpaceId() { +// const rootId = this.getRootId(); +// const object = S.Detail.get(rootId, rootId, ['spaceId'], true); + +// return object.spaceId; +// }; + +// getRootId() { +// const { rootId, match } = this.props; +// return rootId ? rootId : match.params.id; +// }; + +// getSubId() { +// return S.Record.getSubId(this.getRootId(), 'backlinks'); +// }; +// }); + +// export default PageMainDate; + +function getRootId({ rootId, match }) { + return rootId ? rootId : match.params.id; +}; + +const PageMainDate = observer((props: { rootId: string, isPopup: boolean }) => { + const { id } = useParams<{ id: string }>(); + const { rootId = id } = props; + + const [lastId, setLastId] = useState(rootId); + const [isDeleted, setIsDeleted] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const object = S.Detail.get(rootId, rootId, ['backlinks']); + + const subId = S.Record.getSubId(rootId, 'backlinks'); + + const spaceId = S.Detail.get(rootId, rootId, ['spaceId'], true).spaceId; + + const total = S.Record.getMeta(subId, '').total; + + const filters: I.Filter[] = [{ relationKey: 'id', condition: I.FilterCondition.In, value: object.backlinks || [] }]; + + const columns: any[] = [ + { relationKey: 'type', name: translate('commonObjectType'), isObject: true }, + { relationKey: 'creator', name: translate('commonOwner'), isObject: true }, + ]; + + const mounted = useRef(false); + + + const close = () => { + if (!mounted.current) { + return; + } + if (!lastId) { + return; + }; + + const { isPopup } = props; + + const { id } = useParams<{ id: string }>(); + + let close = true; + if (isPopup && (id == lastId)) { + close = false; + }; + + if (close) { + Action.pageClose(lastId, true); + }; + }; + + const open = async () => { + + if (!mounted.current) { + return; + } + + if (lastId == rootId) { + return; + }; + + close(); + setLastId(rootId); + setIsDeleted(false); + setIsLoading(true); + + const message: { error?: { code?: number } } = await then(C.ObjectOpen)(rootId, '', U.Router.getRouteSpaceId()); + + if (!rootId) return; + + const errorCode = message?.error?.code; + + if (errorCode) return; + + setIsLoading(false); + + let hasError = false; + + if (!U.Common.checkErrorCommon(errorCode)) { + hasError = false; + }; + + if ([J.Error.Code.NOT_FOUND, J.Error.Code.OBJECT_DELETED].includes(errorCode)) { + setIsDeleted(true); + } else { + const logPath = U.Common.getElectron().logPath(); + + S.Popup.open('confirm', { + data: { + icon: 'error', + bgColor: 'red', + title: translate('commonError'), + text: translate('popupConfirmObjectOpenErrorText'), + textConfirm: translate('popupConfirmObjectOpenErrorButton'), + onConfirm: () => { + C.DebugTree(rootId, logPath, (message: any) => { + if (!errorCode) { + Renderer.send('openPath', logPath); + }; + }); + + U.Space.openDashboard('route', { replace: true }); + }, + }, + }); + }; + + hasError = false; + + if (!hasError) { + return; + }; + + const object = S.Detail.get(rootId, rootId, []); + setIsLoading(false); + + if (object.isDeleted) { + setIsDeleted(true); + return; + }; + }; + + useEffect(() => { + if (!mounted.current) { + // did mount + mounted.current = true; + } + open(); + return () => { + // will unmount + close(); + mounted.current = false; + }; + }, [rootId]); + + return ( +
+
+ + + {!object._empty_ ? ( +
+
{total} {U.Common.plural(total, translate('pluralObject'))}
+
+ +
+
+ ) : ''} +
+ +
+
+ ); +}); + +export default PageMainDate; \ No newline at end of file diff --git a/src/ts/component/page/main/date/relations.tsx b/src/ts/component/page/main/date/relations.tsx new file mode 100644 index 0000000000..7ec71b3433 --- /dev/null +++ b/src/ts/component/page/main/date/relations.tsx @@ -0,0 +1,127 @@ +import * as React from 'react'; +import $ from 'jquery'; +import arrayMove from 'array-move'; +import { observer } from 'mobx-react'; +import { observable } from 'mobx'; +import { SortableContainer, SortableElement } from 'react-sortable-hoc'; +import { Icon, Button, Filter } from 'Component'; +import { C, I, S, U, M, analytics, Relation, keyboard, translate, Dataview, sidebar, J } from 'Lib'; +import { RelationListWithValue } from 'Lib/api/response'; +import type { RelationWithCounter } from 'Lib/api/response'; +import { useEffect } from 'react'; +// import HeadSimple from 'Component/page/elements/head/simple'; + + +export interface RelationsSelectorProps { + rootId?: string; + relationId?: I.Block; + className?: string; +}; + +// type RelationsSelectorState = ReturnType; +// const RelationsSelector: RelationsSelectorComponent = observer(class Controls extends React.Component { + +// _isMounted = false; +// node: any = null; +// refFilter = null; +// refHead = null; + +// constructor(props: RelationsSelectorProps) { + // super(props); + // this.state = { + // relations: [], + // }; + // }; + + // componentDidMount() { + // this._isMounted = true; + // C.RelationListWithValue(U.Router.getRouteSpaceId(), this.props.rootId, (message: any) => { }); + // // C.RelationListWithValue(U.Router.getRouteSpaceId(), rootId, (message: any) => { + // // const { error } = message; + // // if (error) { + // // return; + // // }; + // // const object = S.Detail.get(this.props.rootId, U.Router.getRouteSpaceId()); + // // console.log('Object:', object); + // // const { countersList, relationskeysList } = message; + // // const relationKeyWithCounterList = relationskeysList.map((relationKey: string, ndx: number) => { + // // return { + // // relationKey, + // // counter: countersList[ndx], + // // }; + // // }); + // // console.log('RelationKeyWithCounterList:', relationKeyWithCounterList); + // // this.setState({ relationKeyWithCounterList: message.relationKeyWithCounterList }); + // // }); + // }; + + // }); + + +const RelationButton = SortableElement(({ relation, rootId }) => { + const elementId = `date-relation-item-${relation.id}`; + return ( +
console.log(relation)} + > + {relation.name || translate('defaultNamePage')} +
+ ); +}); + +const RelationsContainer = SortableContainer(({ relations, rootId }: { relations: RelationWithCounter[], rootId: string }) => ( +
+ {relations.map((relation, i: number) => ( + + ))} +
+)); + +const RelationsSelector: React.FC = (props: RelationsSelectorProps) => { + + const [relations, setRelations] = React.useState([]); + + const { rootId, className } = props; + + const cn: string[] = ['dateRelations']; + if (className) { + cn.push(className); + }; + + useEffect(() => { + C.RelationListWithValue(U.Router.getRouteSpaceId(), rootId, (message: any) => { + const { error } = message; + if (error) { + return; + }; + const object = S.Detail.get(rootId, U.Router.getRouteSpaceId()); + console.log('Object:', object); + const { countersList, relationskeysList } = message; + const relationKeyWithCounterList = relationskeysList.map((relationKey: string, ndx: number) => { + return { + relationkey: relationKey, + counter: countersList[ndx], + }; + }); + console.log('RelationKeyWithCounterList:', relationKeyWithCounterList); + setRelations(relationKeyWithCounterList); + }); + }, [rootId]); + + return ( + + ); +}; + +export default RelationsSelector; diff --git a/src/ts/component/page/main/object.tsx b/src/ts/component/page/main/object.tsx index cfefda4725..4eb93032a9 100644 --- a/src/ts/component/page/main/object.tsx +++ b/src/ts/component/page/main/object.tsx @@ -31,4 +31,4 @@ class PageMainObject extends React.Component { }; -export default PageMainObject; \ No newline at end of file +export default PageMainObject; diff --git a/src/ts/component/page/main/tag.tsx b/src/ts/component/page/main/tag.tsx new file mode 100644 index 0000000000..6beb87d82b --- /dev/null +++ b/src/ts/component/page/main/tag.tsx @@ -0,0 +1,182 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; +import { Action, C, I, S, translate, U } from 'Lib'; +import { Header, Footer, ListObject } from 'Component'; +import HeadSimple from 'Component/page/elements/head/simple'; + +interface State { + isLoading: boolean; + isDeleted: boolean; +}; + +const PageMainTag = observer(class PageMainTag extends React.Component { + + _isMounted = false; + node: any = null; + id = ''; + refHeader: any = null; + refHead: any = null; + + state = { + isLoading: false, + isDeleted: false, + }; + + constructor (props: I.PageComponent) { + super(props); + + this.setColor = this.setColor.bind(this); + }; + + render () { + const rootId = this.getRootId(); + const object = S.Detail.get(rootId, rootId, [ 'backlinks' ]); + + const subId = this.getSubId(); + const total = S.Record.getMeta(subId, '').total; + + const filters: I.Filter[] = [ { relationKey: 'id', condition: I.FilterCondition.In, value: object.backlinks || [] } ]; + + const columns: any[] = [ + { relationKey: 'type', name: translate('commonObjectType'), isObject: true }, + { + relationKey: 'createdDate', name: translate('commonDateCreated'), + mapper: v => v ? U.Date.dateWithFormat(I.DateFormat.MonthAbbrBeforeDay, v) : '', + }, + { relationKey: 'creator', name: translate('commonOwner'), isObject: true }, + ]; + + return ( +
this.node = node}> +
this.refHeader = ref} + rootId={rootId} + /> + +
+ this.refHead = ref} + placeholder={translate('defaultNameTag')} + rootId={rootId} + isContextMenuDisabled={true} + noIcon={true} + withColorPicker={true} + onColorChange={this.setColor} + colorPickerTitle={translate('pageMainTagTagColor')} + /> + + {!object._empty_ ? ( +
+
{total} {U.Common.plural(total, translate('pluralObject'))}
+
+ +
+
+ ) : ''} +
+ +
+
+ ); + }; + + componentDidMount () { + this._isMounted = true; + this.open(); + }; + + componentDidUpdate () { + this.open(); + }; + + componentWillUnmount () { + this._isMounted = false; + this.close(); + }; + + open () { + const rootId = this.getRootId(); + + if (this.id == rootId) { + return; + }; + + this.close(); + this.id = rootId; + this.setState({ isLoading: true}); + + 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, isLoading: false }); + return; + }; + + this.refHeader?.forceUpdate(); + this.refHead?.forceUpdate(); + this.setState({ isLoading: false }); + }); + }; + + 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); + }; + }; + + setColor (color: string) { + const rootId = this.getRootId(); + const object = S.Detail.get(rootId, rootId); + + C.ObjectListSetDetails([ object.id ], [ + { key: 'relationOptionColor', value: color }, + ], (message) => { + S.Detail.update(rootId, { id: object.id, details: { relationOptionColor: color } }, false); + this.forceUpdate(); + }); + }; + + getSpaceId () { + const rootId = this.getRootId(); + const object = S.Detail.get(rootId, rootId, [ 'spaceId' ], true); + + return object.spaceId; + }; + + getRootId () { + const { rootId, match } = this.props; + return rootId ? rootId : match.params.id; + }; + + getSubId () { + return S.Record.getSubId(this.getRootId(), 'backlinks'); + }; +}); + +export default PageMainTag; diff --git a/src/ts/component/page/main/type.tsx b/src/ts/component/page/main/type.tsx index 75ff3b2dc8..7cb3c8df46 100644 --- a/src/ts/component/page/main/type.tsx +++ b/src/ts/component/page/main/type.tsx @@ -348,7 +348,8 @@ const PageMainType = observer(class PageMainType extends React.Component { break; }; + case I.ObjectLayout.Tag: { + break; + }; + case I.ObjectLayout.Collection: case I.ObjectLayout.Set: { if (iconImage) { @@ -507,4 +511,4 @@ const IconObject = observer(class IconObject extends React.Component { }); -export default IconObject; \ No newline at end of file +export default IconObject; diff --git a/src/ts/interface/object.ts b/src/ts/interface/object.ts index d7ca2cc073..9794885a4a 100644 --- a/src/ts/interface/object.ts +++ b/src/ts/interface/object.ts @@ -21,6 +21,7 @@ export enum ObjectLayout { Participant = 19, Pdf = 20, Chat = 22, + Tag = 23, Empty = 100, Navigation = 101, @@ -71,4 +72,4 @@ export enum ObjectOrigin { Usecase = 6, Builtin = 7, Bookmark = 8, -}; \ No newline at end of file +}; diff --git a/src/ts/lib/api/response.ts b/src/ts/lib/api/response.ts index 5de5b4cb04..6e0c7993e5 100644 --- a/src/ts/lib/api/response.ts +++ b/src/ts/lib/api/response.ts @@ -580,4 +580,10 @@ export const ChatAddMessage = (response: Rpc.Chat.AddMessage.Response) => { return { messageId: response.getMessageid(), }; +}; + +export const RelationListWithValue = (response: Rpc.Relation.ListWithValue.Response) => { + return { + listList: response.getListList(), + }; }; \ No newline at end of file diff --git a/src/ts/lib/relation.ts b/src/ts/lib/relation.ts index 3655dd6ea7..a62fd96463 100644 --- a/src/ts/lib/relation.ts +++ b/src/ts/lib/relation.ts @@ -653,4 +653,4 @@ class Relation { }; -export default new Relation(); \ No newline at end of file +export default new Relation(); diff --git a/src/ts/lib/util/data.ts b/src/ts/lib/util/data.ts index ee21cc2234..f7c7160f0e 100644 --- a/src/ts/lib/util/data.ts +++ b/src/ts/lib/util/data.ts @@ -631,7 +631,8 @@ class UtilData { break; case I.ObjectLayout.Bookmark: - case I.ObjectLayout.Task: { + case I.ObjectLayout.Task: + case I.ObjectLayout.Tag: { break; }; diff --git a/src/ts/lib/util/menu.ts b/src/ts/lib/util/menu.ts index b6fa5f2b8f..c8201127a3 100644 --- a/src/ts/lib/util/menu.ts +++ b/src/ts/lib/util/menu.ts @@ -1018,8 +1018,8 @@ class UtilMenu { sort = [ { name: translate('sidebarObjectSort'), isSection: true }, - { id: I.SortId.Updated, name: translate('sidebarObjectSortUpdated'), relationKey: 'lastModifiedDate', isSort: true, defaultType: I.SortType.Desc }, - { id: I.SortId.Created, name: translate('sidebarObjectSortCreated'), relationKey: 'createdDate', isSort: true, defaultType: I.SortType.Desc }, + { id: I.SortId.Updated, name: translate('commonDateUpdated'), relationKey: 'lastModifiedDate', isSort: true, defaultType: I.SortType.Desc }, + { id: I.SortId.Created, name: translate('commonDateCreated'), relationKey: 'createdDate', isSort: true, defaultType: I.SortType.Desc }, { id: I.SortId.Name, name: translate('commonName'), relationKey: 'name', isSort: true, defaultType: I.SortType.Asc }, ]; }; diff --git a/src/ts/lib/util/object.ts b/src/ts/lib/util/object.ts index 1266c008c1..e588c06a6a 100644 --- a/src/ts/lib/util/object.ts +++ b/src/ts/lib/util/object.ts @@ -26,6 +26,7 @@ class UtilObject { case I.ObjectLayout.Empty: r = 'empty'; break; case I.ObjectLayout.Space: case I.ObjectLayout.Chat: r = 'chat'; break; + case I.ObjectLayout.Tag: r = 'tag'; break; }; return r; }; @@ -364,6 +365,10 @@ class UtilObject { return layout == I.ObjectLayout.File; }; + isTagLayout (layout: I.ObjectLayout): boolean { + return layout == I.ObjectLayout.Tag; + }; + // --------------------------------------------------------- // getPageLayouts (): I.ObjectLayout[] { @@ -385,7 +390,7 @@ class UtilObject { }; getLayoutsWithoutTemplates (): I.ObjectLayout[] { - return [].concat(this.getFileAndSystemLayouts()).concat(this.getSetLayouts()).concat([ I.ObjectLayout.Chat, I.ObjectLayout.Participant ]); + return [].concat(this.getFileAndSystemLayouts()).concat(this.getSetLayouts()).concat([ I.ObjectLayout.Chat, I.ObjectLayout.Participant, I.ObjectLayout.Tag ]); }; getFileAndSystemLayouts (): I.ObjectLayout[] { @@ -456,4 +461,4 @@ class UtilObject { }; -export default new UtilObject(); \ No newline at end of file +export default new UtilObject(); diff --git a/src/ts/lib/util/then.ts b/src/ts/lib/util/then.ts new file mode 100644 index 0000000000..8af848398e --- /dev/null +++ b/src/ts/lib/util/then.ts @@ -0,0 +1,10 @@ +type Fn = (...args: T) => void; +type Callback = (data: D) => void; +type HeadArgsTuple> = F extends Fn ? A : never; +type CallbackDataType> = F extends Fn]> ? D : never; + +export function then>(fnWithCallback: F) { + return (...args: HeadArgsTuple) => new Promise((resolve) => fnWithCallback(...args, (data: CallbackDataType) => { + resolve(data); + })); +} \ No newline at end of file diff --git a/src/ts/store/block.ts b/src/ts/store/block.ts index f2e4e56a7b..d6ffb89872 100644 --- a/src/ts/store/block.ts +++ b/src/ts/store/block.ts @@ -692,4 +692,4 @@ class BlockStore { }; - export const Block: BlockStore = new BlockStore(); \ No newline at end of file + export const Block: BlockStore = new BlockStore(); diff --git a/src/ts/store/detail.ts b/src/ts/store/detail.ts index 1418816308..4f262d47fb 100644 --- a/src/ts/store/detail.ts +++ b/src/ts/store/detail.ts @@ -1,5 +1,5 @@ import { observable, action, set, intercept, makeObservable } from 'mobx'; -import { I, S, U, J, Relation, translate } from 'Lib'; +import { I, S, U, J, Relation, translate, C } from 'Lib'; interface Detail { relationKey: string; @@ -279,6 +279,14 @@ class DetailStore { return this.mapSet(object); }; + private mapTag (object: any) { + object.color = Relation.getStringValue(object.color || object.relationOptionColor || 'default'); + + delete(object.relationOptionColor); + + return object; + }; + private mapSpaceView (object: any) { object.spaceAccessType = Number(object.spaceAccessType) || I.SpaceType.Private; object.spaceAccountStatus = Number(object.spaceAccountStatus) || I.SpaceStatus.Unknown;