1
0
Fork 0
mirror of https://github.com/anyproto/anytype-ts.git synced 2025-06-07 21:47:02 +09:00
anytype-ts/extension/popup/create.tsx
Andrew Simachev 165dc920dc
JS-7034: fix
2025-05-19 10:30:10 +02:00

535 lines
No EOL
13 KiB
TypeScript

import * as React from 'react';
import { observer } from 'mobx-react';
import { observable } from 'mobx';
import arrayMove from 'array-move';
import { getRange, setRange } from 'selection-ranges';
import { Label, Input, Button, Select, Loader, Error, DragBox, Tag, Icon, IconObject } from 'Component';
import { I, C, S, U, J, Relation, keyboard, Storage } from 'Lib';
import Util from '../lib/util';
interface State {
error: string;
isLoading: boolean;
withContent: boolean;
collection: any;
};
const MAX_LENGTH = 320;
const Create = observer(class Create extends React.Component<I.PageComponent, State> {
details: any = {
type: '',
tag: [],
};
node: any = null;
refName: any = null;
refDescription: any = null;
refSpace: any = null;
refType: any = null;
refCollection: any = null;
refComment: any = null;
isCreating = false;
url = '';
state = {
error: '',
isLoading: false,
withContent: true,
collection: null,
};
constructor (props: I.PageComponent) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
this.onSpaceChange = this.onSpaceChange.bind(this);
this.onTypeChange = this.onTypeChange.bind(this);
this.onKeyPress = this.onKeyPress.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.onInput = this.onInput.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onDragEnd = this.onDragEnd.bind(this);
this.onCheckbox = this.onCheckbox.bind(this);
this.onCollection = this.onCollection.bind(this);
this.focus = this.focus.bind(this);
};
render () {
const { error, isLoading, withContent } = this.state;
const { space } = S.Common;
const tags = this.getTagsValue();
const collection = this.state.collection as any;
return (
<div
ref={ref => this.node = ref}
className="page pageCreate"
>
{isLoading ? <Loader type={I.LoaderType.Loader} /> : ''}
<form onSubmit={this.onSubmit}>
<div className="rows">
<div className="row">
<Label text="Title" />
<Input ref={ref => this.refName = ref} />
</div>
<div className="row">
<Label text="Description" />
<Input ref={ref => this.refDescription = ref} />
</div>
<div className="row">
<Label text="Space" />
<Select
id="select-space"
ref={ref => this.refSpace = ref}
value=""
options={[]}
onChange={this.onSpaceChange}
menuParam={{
horizontal: I.MenuDirection.Center,
data: { maxHeight: 180 }
}}
/>
</div>
<div className="row">
<Label text="Save as" />
<Select
id="select-type"
ref={ref => this.refType = ref}
readonly={!space}
value=""
options={[]}
onChange={this.onTypeChange}
menuParam={{
horizontal: I.MenuDirection.Center,
data: { maxHeight: 180 }
}}
/>
</div>
<div className="row withContent" onClick={this.onCheckbox}>
<Icon className={[ 'checkbox', (withContent ? 'active' : '') ].join(' ')} />
<Label text="Add page content" />
</div>
<div className="row">
<Label text="Link to Collection" />
<div id="select-collection" className="select" onMouseDown={this.onCollection}>
<div className="item">
{collection ? <IconObject object={collection} iconSize={16} /> : ''}
<div className="name">{collection ? collection.name : 'Select Object'}</div>
</div>
<Icon className="arrow light" />
</div>
</div>
<div className="row">
<Label text="Tag" />
<div id="select-tag" className="box cell isEditing c-select" onClick={this.focus}>
<div className="value cellContent c-select">
<span id="list">
<DragBox onDragEnd={this.onDragEnd}>
{tags.map((item: any, i: number) => (
<span
key={i}
id={`item-${item.id}`}
className="itemWrap isDraggable"
draggable={true}
{...U.Common.dataProps({ id: item.id, index: i })}
>
<Tag
key={item.id}
text={item.name}
color={item.color}
canEdit={true}
className={Relation.selectClassName(I.RelationType.MultiSelect)}
onRemove={() => this.onValueRemove(item.id)}
/>
</span>
))}
</DragBox>
</span>
<span className="entryWrap">
<span
id="entry"
contentEditable={true}
suppressContentEditableWarning={true}
onFocus={this.onFocus}
onInput={this.onInput}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
>
{'\n'}
</span>
<div id="placeholder" className="placeholder">Type...</div>
</span>
</div>
</div>
</div>
</div>
<div className="buttonsWrapper">
<Button color="pink" className="c32" text="Save" type="input" subType="submit" onClick={this.onSubmit} />
</div>
<Error text={error} />
</form>
</div>
);
};
componentDidMount (): void {
U.Subscription.createAll(() => {
this.initSpace();
this.initName();
this.initType();
this.initCollection();
this.setState({ withContent: Boolean(Storage.get('withContent')) });
});
};
componentDidUpdate(): void {
this.initType();
this.initCollection();
this.placeholderCheck();
};
initSpace () {
const spaces = this.getSpaces();
if (!this.refSpace || !spaces.length) {
return;
};
let spaceId = S.Common.space || Storage.get('lastSpaceId');
if (!spaceId) {
spaceId = spaces[0].id;
};
this.refSpace.setOptions(spaces);
this.refSpace.setValue(spaceId);
this.onSpaceChange(spaceId);
};
initType () {
const options = this.getTypes().map(it => ({ ...it, id: it.uniqueKey }));
if (!this.refType || !options.length) {
return;
};
this.details.type = this.details.type || J.Constant.typeKey.bookmark;
this.refType.setOptions(options);
this.refType.setValue(this.details.type);
};
initCollection () {
const { collection } = this.details;
if (!collection) {
return;
};
U.Object.getById(collection, {}, object => this.setState({ collection: object }));
};
initName () {
if (!this.refName) {
return;
};
Util.getCurrentTab(tab => {
if (!tab) {
return;
};
this.refName.setValue(tab.title);
this.refName.focus();
this.url = tab.url;
});
};
getObjects (subId: string) {
return S.Record.getRecords(subId);
};
getSpaces () {
return U.Space.getList()
.filter(it => it && U.Space.canMyParticipantWrite(it.targetSpaceId))
.map(it => ({ ...it, id: it.targetSpaceId, object: it }));
};
getTypes () {
const layouts = U.Object.getPageLayouts();
return this.getObjects(J.Constant.subId.type).
map(Util.optionMapper).
filter(this.filter).
filter(it => layouts.includes(it.recommendedLayout) && (it.spaceId == S.Common.space) && (it.uniqueKey != J.Constant.typeKey.template)).
sort(U.Data.sortByName);
};
filter (it: any) {
return it && !it.isHidden && !it.isArchived && !it.isDeleted;
};
onTypeChange (id: string): void {
this.details.type = id;
this.forceUpdate();
};
onCollection (): void {
const collectionType = S.Record.getCollectionType();
S.Menu.open('searchObject', {
className: 'single',
element: '#select-collection',
data: {
filters: [
{ relationKey: 'resolvedLayout', condition: I.FilterCondition.In, value: I.ObjectLayout.Collection },
{ relationKey: 'type.uniqueKey', condition: I.FilterCondition.NotIn, value: [ J.Constant.typeKey.template ] },
{ relationKey: 'isReadonly', condition: I.FilterCondition.NotEqual, value: true },
],
onSelect: (el: any) => {
this.setState({ collection: el });
},
canAdd: true,
addParam: {
name: 'Create new Collection',
nameWithFilter: 'Create new Collection "%s"',
onClick: (details: any) => {
C.ObjectCreate(details, [], '', collectionType?.uniqueKey, S.Common.space, message => {
if (message.error.code) {
this.setState({ error: message.error.description });
return;
};
this.setState({ collection: message.details });
});
},
},
}
});
};
onSpaceChange (id: string): void {
S.Common.spaceSet(id);
U.Subscription.createAll(() => this.forceUpdate());
Storage.set('lastSpaceId', id);
};
getTagsValue () {
return S.Record.getRecordIds(J.Constant.subId.option, '').
filter(id => this.details.tag.includes(id)).
map(id => S.Detail.get(J.Constant.subId.option, id)).
filter(it => it && !it._empty_);
};
clear () {
const node = $(this.node);
node.find('#entry').text(' ');
this.focus();
};
focus () {
const node = $(this.node);
const entry = node.find('#entry');
if (entry.length) {
window.setTimeout(() => {
entry.focus();
setRange(entry.get(0), { start: 0, end: 0 });
this.scrollToBottom();
});
};
};
onValueRemove (id: string) {
this.setValue(this.details.tag.filter(it => it != id));
};
onDragEnd (oldIndex: number, newIndex: number) {
this.setValue(arrayMove(this.details.tag, oldIndex, newIndex));
};
onKeyDown (e: any) {
const node = $(this.node);
const entry = node.find('#entry');
keyboard.shortcut('backspace', e, (pressed: string) => {
e.stopPropagation();
const range = getRange(entry.get(0));
if (range.start || range.end) {
return;
};
e.preventDefault();
this.details.tag.pop();
this.setValue(this.details.tag);
});
this.placeholderCheck();
this.scrollToBottom();
};
onKeyPress (e: any) {
const node = $(this.node);
const entry = node.find('#entry');
if (entry.length && (entry.text().length >= MAX_LENGTH)) {
e.preventDefault();
};
};
onKeyUp (e: any) {
S.Menu.updateData('dataviewOptionList', { filter: this.getValue() });
this.placeholderCheck();
this.resize();
this.scrollToBottom();
};
onInput () {
this.placeholderCheck();
};
onFocus () {
const relation = S.Record.getRelationByKey('tag');
const element = '#select-tag';
S.Menu.open('dataviewOptionList', {
element,
horizontal: I.MenuDirection.Center,
commonFilter: true,
onOpen: () => {
window.setTimeout(() => $(element).addClass('isFocused'));
},
onClose: () => $(element).removeClass('isFocused'),
data: {
canAdd: true,
canEdit: true,
filter: '',
value: this.details.tag,
maxCount: relation.maxCount,
noFilter: true,
relation: observable.box(relation),
maxHeight: 120,
onChange: (value: string[]) => {
this.setValue(value);
}
}
});
};
placeholderCheck () {
const node = $(this.node);
const value = this.getValue();
const list = node.find('#list');
const placeholder = node.find('#placeholder');
const length = this.details.tag.length;
length ? list.show() : list.hide();
value || length ? placeholder.hide() : placeholder.show();
};
getValue (): string {
const node = $(this.node);
const entry = node.find('#entry');
return entry.length ? String(entry.text() || '').trim() : '';
};
setValue (value: string[]) {
const relation = S.Record.getRelationByKey('tag');
value = U.Common.arrayUnique(value);
const length = value.length;
if (relation.maxCount && (length > relation.maxCount)) {
value = value.slice(length - relation.maxCount, length);
};
this.details.tag = value;
this.clear();
this.forceUpdate();
};
onSubmit (e: any) {
e.preventDefault();
if (this.isCreating) {
return;
};
const { withContent } = this.state;
const collection = this.state.collection as any;
this.isCreating = true;
this.setState({ isLoading: true, error: '' });
const details = Object.assign({
name: this.refName?.getValue(),
description: this.refDescription?.getValue(),
origin: I.ObjectOrigin.Webclipper,
}, this.details);
const type = S.Record.getTypeByKey(details.type);
delete(details.type);
C.ObjectCreateFromUrl(details, S.Common.space, type?.uniqueKey, this.url, withContent, type?.defaultTemplateId, (message: any) => {
this.setState({ isLoading: false });
if (message.error.code) {
this.setState({ error: message.error.description });
return;
};
const object = message.details;
if (collection) {
C.ObjectCollectionAdd(collection.id, [ object.id ], () => {
this.setState({ collection: null });
});
};
S.Extension.createdObject = object;
U.Router.go('/success', {});
this.isCreating = false;
});
};
onCheckbox () {
const { withContent } = this.state;
Storage.set('withContent', !withContent);
this.setState({ withContent: !withContent });
};
scrollToBottom () {
const node = $(this.node);
const content: any = node.find('.cellContent');
content.scrollTop(content.get(0).scrollHeight + parseInt(content.css('paddingBottom')));
};
resize () {
$(window).trigger('resize.menuDataviewOptionList');
};
});
export default Create;