Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract Composer state #2161

Merged
merged 62 commits into from
Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
4ab390a
Extract ComposerState
askvortsov1 May 11, 2020
ce17a9d
Add in small BC layer to slightly reduce chaos
askvortsov1 May 11, 2020
fd80ad9
Slightly expand BC layer to work with mentions
askvortsov1 May 11, 2020
73f2975
Encapsulate body subclass in composer state
askvortsov1 Jun 7, 2020
889135b
Move bodyClass and bodyProps out into a POJO
askvortsov1 Jun 7, 2020
98603ed
Cleanup of data relationships
askvortsov1 Jun 7, 2020
e5dc2dc
move composingReplyTo into ComposerState
askvortsov1 Jun 7, 2020
f3ce442
Add doc blocks for some deprecated stuff
askvortsov1 Jun 18, 2020
050459d
Add one more deprecated docblock
askvortsov1 Jun 18, 2020
a48e5ef
refactor composer load to use the composer instance internally
askvortsov1 Jun 19, 2020
30fe5df
Fix operator typo
askvortsov1 Jun 19, 2020
257783c
Remove obsolete imports
franzliedke Jul 3, 2020
5a53e43
ComposerState: Encapsulate a new bodyMatches() method
franzliedke Jul 7, 2020
485f052
Fix discussion state directory (not sure why this is present in this PR)
askvortsov1 Jul 10, 2020
712482c
Instead of js-TextEditor, have the texteditor provide it's jquery-sel…
askvortsov1 Jul 10, 2020
5b6d305
Remove unnecessary use of mithril stream in TextEditor
askvortsov1 Jul 10, 2020
3557c2f
Replace position absolute check for fullscreen with explicit screen w…
askvortsov1 Jul 10, 2020
de6c82b
Move some height-related things into Composer
askvortsov1 Jul 10, 2020
b091253
Move event-based composer interaction API into FSM in Composer compon…
askvortsov1 Jul 10, 2020
04f5c46
Get rid of composer "instance" abstraction
askvortsov1 Jul 10, 2020
58dacea
Improve and simplify composer.js focus selector
askvortsov1 Jul 10, 2020
6b0064a
Rearrange some methods on Composer and ComposerState
askvortsov1 Jul 10, 2020
dbba0ba
Fix and simplify animatePositionChange
askvortsov1 Jul 10, 2020
6393304
Remove this.disabled change
askvortsov1 Jul 10, 2020
af9fa81
Put back prop disabled; put body into const (cleanup), use unpacking …
askvortsov1 Jul 10, 2020
51e1465
Remove irrelevant (and distracting) comments
askvortsov1 Jul 10, 2020
aed0edb
Move data() methods back to subclasses
franzliedke Jul 10, 2020
f91adc4
Move method
franzliedke Jul 10, 2020
33d020e
Call composer state via prop, not global app
franzliedke Jul 10, 2020
2de2dea
Rename "state" prop to "composer"
franzliedke Jul 10, 2020
294ad0d
Rename PositionEnum to Position
franzliedke Jul 10, 2020
efc3ab7
More type hints and docblocks
franzliedke Jul 10, 2020
1606c87
Remove getBody()
franzliedke Jul 10, 2020
e26c32d
Add isVisible() method to ComposerState
franzliedke Jul 10, 2020
3db703c
More documentation
franzliedke Jul 10, 2020
26837a2
active is UI state, so it can be stored in the component
franzliedke Jul 10, 2020
2e84cc5
Fix comment
franzliedke Jul 10, 2020
44a6348
Restore a comment that was useful
franzliedke Jul 10, 2020
0cd2817
Encapsulate composer animations into smaller methods
franzliedke Jul 10, 2020
a00cb24
Rename / reorder methods for better diff
franzliedke Jul 10, 2020
e0c95c8
Reduce size of diff :)
franzliedke Jul 12, 2020
da3e4dd
Move methods to old positions
franzliedke Jul 12, 2020
7969e57
focus(): Use precise selector to restore old behavior
franzliedke Jul 17, 2020
4d46be0
Shrink diff :)
franzliedke Jul 17, 2020
02e9439
Document and reset bodyPreventExit
franzliedke Jul 17, 2020
b2e1fc6
Early return for consistency
franzliedke Jul 17, 2020
2f6712a
Fix typo
franzliedke Jul 17, 2020
ddbaabc
Do not leak onbeforeunload handler
franzliedke Jul 17, 2020
9429fb2
Fix and simplify discussion title initialization code
franzliedke Jul 17, 2020
050081c
Rework Composer exit prevention logic
franzliedke Jul 17, 2020
a13abf7
Only ask for confirmation when we have a question
franzliedke Jul 17, 2020
8fed2d7
Stop using deprecated fields
franzliedke Jul 17, 2020
7beb807
Rename method for more expressiveness
franzliedke Jul 17, 2020
553afa1
Fix typehint
franzliedke Jul 17, 2020
0c6b264
Extract textarea manipulation back into separate object
franzliedke Jul 17, 2020
9909dd6
Encapsulation
franzliedke Jul 17, 2020
33535a5
SuperTextarea: Add more useful methods for extensions
franzliedke Jul 24, 2020
c8e78df
FIX: DiscussionComposer should not have a "Jump to preview" button
franzliedke Jul 24, 2020
c60bc91
Expose responsive screen mode as CSS variable
franzliedke Jul 24, 2020
96aae3d
Avoid hardcoding the mobile breakpoint
franzliedke Jul 24, 2020
7667761
Reference correct css variable
askvortsov1 Jul 24, 2020
3407dcb
Fix leftover reference to app.current.stream (should be app.current.g…
askvortsov1 Jul 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions js/src/common/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,16 @@ export default class Application {
return null;
}

/**
* Determine the current screen mode, based on our media queries.
*
* @returns {String} - one of "phone", "tablet", "desktop" or "desktop-hd"
*/
screen() {
const styles = getComputedStyle(document.documentElement);
return styles.getPropertyValue('--flarum-screen');
}

/**
* Set the <title> of the page.
*
Expand Down
37 changes: 37 additions & 0 deletions js/src/common/components/ConfirmDocumentUnload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Component from '../Component';

/**
* The `ConfirmDocumentUnload` component can be used to register a global
* event handler that prevents closing the browser window/tab based on the
* return value of a given callback prop.
*
* ### Props
*
* - `when` - a callback returning true when the browser should prompt for
* confirmation before closing the window/tab
*
* ### Children
*
* NOTE: Only the first child will be rendered. (Use this component to wrap
* another component / DOM element.)
*
*/
export default class ConfirmDocumentUnload extends Component {
config(isInitialized, context) {
if (isInitialized) return;

const handler = () => this.props.when() || undefined;

$(window).on('beforeunload', handler);

context.onunload = () => {
$(window).off('beforeunload', handler);
};
}

view() {
// To avoid having to render another wrapping <div> here, we assume that
// this component is only wrapped around a single element / component.
return this.props.children[0];
}
}
109 changes: 109 additions & 0 deletions js/src/common/utils/SuperTextarea.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* A textarea wrapper with powerful helpers for text manipulation.
*
* This wraps a <textarea> DOM element and allows directly manipulating its text
* contents and cursor positions.
*
* I apologize for the pretentious name. :)
*/
export default class SuperTextarea {
/**
* @param {HTMLTextAreaElement} textarea
*/
constructor(textarea) {
this.el = textarea;
this.$ = $(textarea);
}

/**
* Set the value of the text editor.
*
* @param {String} value
*/
setValue(value) {
this.$.val(value).trigger('input');
}

/**
* Focus the textarea and place the cursor at the given index.
*
* @param {number} position
*/
moveCursorTo(position) {
this.setSelectionRange(position, position);
}

/**
* Get the selected range of the textarea.
*
* @return {Array}
*/
getSelectionRange() {
return [this.el.selectionStart, this.el.selectionEnd];
}

/**
* Insert content into the textarea at the position of the cursor.
*
* @param {String} text
*/
insertAtCursor(text) {
this.insertAt(this.el.selectionStart, text);

this.el.dispatchEvent(new CustomEvent('input', { bubbles: true, cancelable: true }));
}

/**
* Insert content into the textarea at the given position.
*
* @param {number} pos
* @param {String} text
*/
insertAt(pos, text) {
this.insertBetween(pos, pos, text);
}

/**
* Insert content into the textarea between the given positions.
*
* If the start and end positions are different, any text between them will be
* overwritten.
*
* @param start
* @param end
* @param text
*/
insertBetween(start, end, text) {
const value = this.el.value;

const before = value.slice(0, start);
const after = value.slice(end);

this.setValue(`${before}${text}${after}`);

// Move the textarea cursor to the end of the content we just inserted.
this.moveCursorTo(start + text.length);
}

/**
* Replace existing content from the start to the current cursor position.
*
* @param start
* @param text
*/
replaceBeforeCursor(start, text) {
this.insertBetween(start, this.el.selectionStart, text);
}

/**
* Set the selected range of the textarea.
*
* @param {number} start
* @param {number} end
* @private
*/
setSelectionRange(start, end) {
this.el.setSelectionRange(start, end);
this.$.focus();
}
}
24 changes: 7 additions & 17 deletions js/src/forum/ForumApplication.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import History from './utils/History';
import Pane from './utils/Pane';
import ReplyComposer from './components/ReplyComposer';
import DiscussionPage from './components/DiscussionPage';
import SignUpModal from './components/SignUpModal';
import HeaderPrimary from './components/HeaderPrimary';
Expand All @@ -16,6 +15,7 @@ import Navigation from '../common/components/Navigation';
import NotificationListState from './states/NotificationListState';
import GlobalSearchState from './states/GlobalSearchState';
import DiscussionListState from './states/DiscussionListState';
import ComposerState from './states/ComposerState';

export default class ForumApplication extends Application {
/**
Expand Down Expand Up @@ -73,6 +73,11 @@ export default class ForumApplication extends Application {
*/
search = new GlobalSearchState();

/*
* An object which controls the state of the composer.
*/
composer = new ComposerState();

constructor() {
super();

Expand Down Expand Up @@ -114,9 +119,9 @@ export default class ForumApplication extends Application {
m.mount(document.getElementById('header-navigation'), Navigation.component());
m.mount(document.getElementById('header-primary'), HeaderPrimary.component());
m.mount(document.getElementById('header-secondary'), HeaderSecondary.component());
m.mount(document.getElementById('composer'), Composer.component({ state: this.composer }));

this.pane = new Pane(document.getElementById('app'));
this.composer = m.mount(document.getElementById('composer'), Composer.component());

m.route.mode = 'pathname';
super.mount(this.forum.attribute('basePath'));
Expand All @@ -138,21 +143,6 @@ export default class ForumApplication extends Application {
});
}

/**
* Check whether or not the user is currently composing a reply to a
* discussion.
*
* @param {Discussion} discussion
* @return {Boolean}
*/
composingReplyTo(discussion) {
return (
this.composer.component instanceof ReplyComposer &&
this.composer.component.props.discussion === discussion &&
this.composer.position !== Composer.PositionEnum.HIDDEN
askvortsov1 marked this conversation as resolved.
Show resolved Hide resolved
);
}

/**
* Check whether or not the user is currently viewing a discussion.
*
Expand Down
4 changes: 2 additions & 2 deletions js/src/forum/components/CommentPost.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default class CommentPost extends Post {
}

isEditing() {
return app.composer.component instanceof EditPostComposer && app.composer.component.props.post === this.props.post;
return app.composer.bodyMatches(EditPostComposer, { post: this.props.post });
}

attrs() {
Expand Down Expand Up @@ -105,7 +105,7 @@ export default class CommentPost extends Post {
// body with a preview.
let preview;
const updatePreview = () => {
const content = app.composer.component.content();
const content = app.composer.fields.content();

if (preview === content) return;

Expand Down
Loading