2023-12-12 09:58:20 +00:00
|
|
|
import * as css from './styles';
|
2024-01-18 17:23:50 +00:00
|
|
|
import {BoxModel} from 'app/client/components/Forms/Model';
|
2023-12-12 09:58:20 +00:00
|
|
|
import {textarea} from 'app/client/ui/inputs';
|
|
|
|
import {theme} from 'app/client/ui2018/cssVars';
|
2024-01-18 17:23:50 +00:00
|
|
|
import {not} from 'app/common/gutil';
|
2023-12-12 09:58:20 +00:00
|
|
|
import {Computed, dom, Observable, styled} from 'grainjs';
|
2024-01-18 17:23:50 +00:00
|
|
|
import {buildEditor} from 'app/client/components/Forms/Editor';
|
2023-12-12 09:58:20 +00:00
|
|
|
|
|
|
|
export class ParagraphModel extends BoxModel {
|
|
|
|
public edit = Observable.create(this, false);
|
|
|
|
|
2024-01-18 17:23:50 +00:00
|
|
|
protected defaultValue = '**Lorem** _ipsum_ dolor';
|
|
|
|
protected cssClass = '';
|
|
|
|
|
|
|
|
private _overlay = Computed.create(this, not(this.selected));
|
|
|
|
|
|
|
|
public override render(): HTMLElement {
|
2023-12-12 09:58:20 +00:00
|
|
|
const box = this;
|
|
|
|
const editMode = box.edit;
|
|
|
|
let element: HTMLElement;
|
2024-01-18 17:23:50 +00:00
|
|
|
const text = this.prop('text', this.defaultValue) as Observable<string|undefined>;
|
2023-12-12 09:58:20 +00:00
|
|
|
|
2024-01-18 17:23:50 +00:00
|
|
|
// There is a spacial hack here. We might be created as a separator component, but the rendering
|
|
|
|
// for separator looks bad when it is the only content, so add a special case for that.
|
|
|
|
const isSeparator = Computed.create(this, (use) => use(text) === '---');
|
2023-12-12 09:58:20 +00:00
|
|
|
|
2024-01-18 17:23:50 +00:00
|
|
|
return buildEditor({
|
|
|
|
box: this,
|
|
|
|
overlay: this._overlay,
|
|
|
|
content: css.cssMarkdownRendered(
|
|
|
|
css.markdown(use => use(text) || '', dom.hide(editMode)),
|
|
|
|
dom.maybe(use => !use(text) && !use(editMode), () => cssEmpty('(empty)')),
|
|
|
|
css.cssMarkdownRendered.cls('-separator', isSeparator),
|
|
|
|
dom.on('click', () => {
|
|
|
|
if (!editMode.get() && this.selected.get()) {
|
|
|
|
editMode.set(true);
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
css.cssMarkdownRendered.cls('-edit', editMode),
|
|
|
|
css.cssMarkdownRendered.cls(u => `-alignment-${u(box.prop('alignment', 'left'))}`),
|
|
|
|
this.cssClass ? dom.cls(this.cssClass, not(editMode)) : null,
|
|
|
|
dom.maybe(editMode, () => {
|
|
|
|
const draft = Observable.create(null, text.get() || '');
|
|
|
|
setTimeout(() => element?.focus(), 10);
|
|
|
|
return [
|
|
|
|
element = cssTextArea(draft, {autoGrow: true, onInput: true},
|
|
|
|
cssTextArea.cls('-edit', editMode),
|
|
|
|
css.saveControls(editMode, (ok) => {
|
|
|
|
if (ok && editMode.get()) {
|
|
|
|
text.set(draft.get());
|
|
|
|
this.save().catch(reportError);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
),
|
|
|
|
];
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
});
|
2023-12-12 09:58:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const cssTextArea = styled(textarea, `
|
|
|
|
color: ${theme.inputFg};
|
|
|
|
background-color: ${theme.mainPanelBg};
|
|
|
|
border: 0px;
|
|
|
|
width: 100%;
|
|
|
|
padding: 3px 6px;
|
|
|
|
outline: none;
|
|
|
|
max-height: 300px;
|
|
|
|
min-height: calc(3em * 1.5);
|
|
|
|
resize: none;
|
|
|
|
border-radius: 3px;
|
2024-01-18 17:23:50 +00:00
|
|
|
&-edit {
|
|
|
|
cursor: auto;
|
|
|
|
background: ${theme.inputBg};
|
|
|
|
outline: 2px solid black;
|
|
|
|
outline-offset: 1px;
|
|
|
|
border-radius: 2px;
|
|
|
|
}
|
2023-12-12 09:58:20 +00:00
|
|
|
&::placeholder {
|
|
|
|
color: ${theme.inputPlaceholderFg};
|
|
|
|
}
|
|
|
|
&[readonly] {
|
|
|
|
background-color: ${theme.inputDisabledBg};
|
|
|
|
color: ${theme.inputDisabledFg};
|
|
|
|
}
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssEmpty = styled('div', `
|
|
|
|
color: ${theme.inputPlaceholderFg};
|
|
|
|
font-style: italic;
|
|
|
|
`);
|