-
Notifications
You must be signed in to change notification settings - Fork 140
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
Add a WYSIWYG-esque editor for instructions #1426
base: master
Are you sure you want to change the base?
Conversation
@outoftime The only thing I still would like to add here is the async component loading we were discussing. I was going to use this article but not sure if react-lazy-load or react-async-componet is better. Did you say there is somewhere in the project you are already doing this? |
@meg2208 Sick! Just took a quick pass through the code and looks great. Hopefully I’ll be able to do a close review this evening. As for the two async libraries—we don’t currently do any async rendering; we do use async loading elsewhere in the code, though it’s more straightforward when you’re not dealing with component rendering. Glancing at the two libraries’ READMEs, I’d be inclined to go with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@meg2208 nice work on this! left a handful of low-level feedback but the high-level structure here looks just right. can’t wait to release this!
@@ -65,6 +65,12 @@ export const startEditingInstructions = createAction( | |||
(_projectKey, timestamp = Date.now()) => ({timestamp}), | |||
); | |||
|
|||
export const continueEditingInstructions = createAction( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn’t sure what continueEditingInstructions
was based on the name (had to look at where it was used)—maybe we could do something more explicit/verbose like updateInProgressInstructions
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is of course the camel case version of the action name, and for the action I was following the pattern of the actions {verb}_EDITING_INSTRUCTIONS
. Other verbs are start
and cancel
. You would rather break that pattern? It just seems a bit less intuitive but I'm fine with it. So it would be UPDATE_IN_PROGRESS_INSTRUCTIONS
? I figure continueEditingInstructions
fits in nicely with startEditingInstructions
and cancelEditingInstructions
. Another idea for something more verbose is continueEditingUnsavedInstructions
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely understand the appeal of the symmetry, but ultimately I think clarity/expressiveness is the overriding concern here. Given that you went with draftInstructions
for the property name (which I like) I would propose updateDraftInstructions
here. WDYT?
this._editor.focus(); | ||
} | ||
bindAll(this, '_handleCancelEditing', '_handleContinueEditing', | ||
'_handleSaveChanges'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Project style for a multi-line function call is one argument per line:
bindAll(
this,
'_handleCancelEditing',
'_handleContinueEditing',
'_handleSaveChanges',
);
src/css/application.css
Outdated
@@ -865,3 +865,11 @@ body { | |||
.u__icon_disabled { | |||
cursor: default; | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The project’s CSS bundling system certainly lacks elegance, but we do want to avoid pasting dependency styles into the first-party stylesheet.
Ideal would be to avoid loading these styles until the instructions editor needs them, maybe something with webpack CSS modules? But probably beyond the scope of this PR.
Most straightforward would be to directly reference the CSS file in the simplemde package, as we do for normalize.css.
src/actions/ui.js
Outdated
export const continueEditingInstructions = createAction( | ||
'CONTINUE_EDITING_INSTRUCTIONS', | ||
(projectKey, content) => ({projectKey, content}), | ||
(_projectKey, timestamp = Date.now()) => ({timestamp}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we use meta.timestamp
from this action? We can save ourselves the trouble of generating it if not.
src/reducers/ui.js
Outdated
@@ -11,6 +11,7 @@ export const DEFAULT_WORKSPACE = new Immutable.Map({ | |||
columnFlex: DEFAULT_COLUMN_FLEX, | |||
rowFlex: DEFAULT_ROW_FLEX, | |||
isDraggingColumnDivider: false, | |||
displayedInstructions: '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This property name might be a bit confusing—I could see it describing the instructions that you see on the screen when just viewing instructions, outside the context of editing.
Maybe something like inProgressInstructions
, draftInstructions
, etc.?
} | ||
|
||
if (isEditing && instructionUnsaved) { | ||
return instructionUnsaved; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what’s the thinking behind changing the semantics of this selector? I’m a little wary of this as the more complex behavior may not be obvious/expected from the call site. If there are situations in which we do want a selector for “bleeding edge instructions” I would propose creating a new selector whose name describes its behavior more explicitly. But I’m down to be convinced otherwise!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did add getCurrentProjectinstructionsUnsaved, and note that this selector invokes that one, but I did like that this was all pulled when the HOC is composed like this:
function mapStateToProps(state) {
return {
instructions: getCurrentProjectInstructions(state),
isEditing: isEditingInstructions(state),
isOpen: !getHiddenUIComponents(state).includes('instructions'),
projectKey: getCurrentProjectKey(state),
};
}
So this selector sort of becomes the router on which instructions to pull, without having to change the existing logic or add another wrapper on top of those two. I think if this needs to get any more complex in the future it would make sense to break it out, but for now I added some comments explaining. If you still want me to update it, happy to do it 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel you on the appeal of having a “router” selector for sure. My concern is just making this selector that router—the name implies the more straightforward previous behavior. An example of where this would be an issue is if getCurrentProjectInstructions
were used somewhere else in the application to decide whether to display a “Delete Instructions” button (based on the selector returning a truthy value). This would yield weird behavior as currently written.
So, I would propose creating a new selector that composes getCurrentProjectInstructions
and getCurrentProjectInstructionsUnsaved
(although again I would go for something like getCurrentProjectDraftInstructions
), and using that here when we explicitly want the “routing behavior”.
That said, I am also not totally convinced we want to be routing the unsaved instructions through the instructions
prop in the <Instructions>
component. Might it be simpler to reason about if the component were explicitly given the instructions for display and the draft instructions for the editor as separate props? In fact, it seems like a draftInstructions
prop could also do the job of isEditing
, i.e. the mere presence of draftInstructions
would be the signal that we are currently editing.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good points. I would be fine with putting it in the render of the <Instructions>
component and passing both values in. It's really just a one-liner.
projectKey ? projects.getIn([projectKey, 'instructions']) : '', | ||
[ | ||
getCurrentProjectKey, getProjects, | ||
isEditingInstructions, getCurrentProjectInstructionsUnsaved, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you agree with my below comment then probably not relevant, but project style would be one array element per line.
@@ -0,0 +1,3 @@ | |||
export default function getCurrentProjectinstructionsUnsaved(state) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small thing, but I think getCurrentProjectUnsavedInstructions
would read a little more naturally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I feel you, but I think clarity is again the first-order concern : )
* Copyright Next Step Webs, Inc. | ||
* @link https://github.com/NextStepWebs/simplemde-markdown-editor | ||
* @license MIT | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we’d like to avoid ever having to vendor dependencies into source control—package managers are of course much easier to work with. In this case I think we should just be able to add 'node_modules/simplemde/dist/simplemde.min.css'
to this list. We’ll also want to add simplemde
itself as a dependency in package.json
so we can guarantee that it will be hoisted to the top level, but that seems fine.
} | ||
|
||
if (isEditing && instructionUnsaved) { | ||
return instructionUnsaved; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel you on the appeal of having a “router” selector for sure. My concern is just making this selector that router—the name implies the more straightforward previous behavior. An example of where this would be an issue is if getCurrentProjectInstructions
were used somewhere else in the application to decide whether to display a “Delete Instructions” button (based on the selector returning a truthy value). This would yield weird behavior as currently written.
So, I would propose creating a new selector that composes getCurrentProjectInstructions
and getCurrentProjectInstructionsUnsaved
(although again I would go for something like getCurrentProjectDraftInstructions
), and using that here when we explicitly want the “routing behavior”.
That said, I am also not totally convinced we want to be routing the unsaved instructions through the instructions
prop in the <Instructions>
component. Might it be simpler to reason about if the component were explicitly given the instructions for display and the draft instructions for the editor as separate props? In fact, it seems like a draftInstructions
prop could also do the job of isEditing
, i.e. the mere presence of draftInstructions
would be the signal that we are currently editing.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GitHub is being a little annoying about comments and diffs but please see:
Feels like we’re getting close! Excited to see what the async rendering looks like.
@meg2208 heyo—I’d love to get this feature released and it feels pretty close! the async loading question came up in another context as well recently and would be great to have your solution in here as a template. any chance you’ve got some time to bring this one home?? |
This closes #1394 and #1397. Please see gif below for a demo. :)