feat(server): add support for drag-drop image upload#2742
Conversation
afa927b to
2b71504
Compare
|
@thseiler, thanks for picking up this feature. I’ve wanted this for a long time. I tested the new image upload flow and noticed three problematic cases. (1) Pasting from the clipboard can create two image blocks instead of one when the pasted image has a default name such as This reproduces when pasting image data from the clipboard or when pasting an image file already named Looking at the code, it seems that (2) When inserting an image into a newly created node before the node is saved for the first time, the editor shows It looks like the client sends the upload request immediately, while (3) If a node already contains an older It looks like the code replaces text in the whole field ( |
|
Small formatting note: the JS file has some trailing spaces and whitespace-only lines, and at least one place where the brace style differs, e.g. This might just be an editor setting, but it would be great to clean those up too. |
|
I also tested the upload failure path by forcing In that case the UI does not replace It looks like the catch block calls |
|
Not for this PR necessarily, but maybe something to keep in mind for later (@stanislaw). Unless I’m missing something, this looks like it can leave orphaned files in
|
|
The new E2E test covers the happy path well, but it might be worth extending coverage to the cases mentioned above. A test for the upload failure path would also be useful. That may require some extra test support to simulate a server error (? @stanislaw), so it could also be tracked separately. |
|
@mettta Thanks a lot for the review & the detailed feedback! (1) This was a copy-paste error; there should have been only one loop there! Sorry, my mistake... I also fixed the formatting of the stimulus controller and added some more test cases to cover:
|
|
@thseiler Looks good, thanks for the changes. I noticed the help text in Should the description be updated to mention images only, or is support for attachments still expected here? |
|
@thseiler By the way, not a blocker for the merge - just something I noticed. I’m not seeing this take effect in any browser: In my tests, the cursor just stays default during a file drag ((it only changes to 'copy' over Did you manage to get this working on your side? As mentioned, not a blocker - the current behavior is fine as is. We can revisit this later in a separate PR. |
|
@mettta The CSS was an attempt to prevent flicker; without it, I would get flicker between the copy icon and the slash icon when dragging over a single-line editable. I found that the flicker was happening due to a missing "dragenter" handler in the controller. I added it and removed the redundant CSS. I updated the help text for the CLI command to indicate that this is images-only. I also noticed that when editing two nodes simultaneously, all uploaded images would be erroneously be attributed to the first node, due to |
|
I noticed that I also need to handle the "@asset" macro for rst & json export. I hope to address this in the coming days... |
071a80e to
9fa6e94
Compare
stanislaw
left a comment
There was a problem hiding this comment.
@thseiler this is great work! I only left a few small comments and would like to ask you:
I have started moving all low-level StrictDoc implementation requirements to the L3/LLR document. Would it be too much if I asked you to create a few L3/LLRs to support the work that you did here?
You could create a section about this drag-and-drop paste feature in L3 and add a small! number of essential requirements that govern your implementation. You could use the existing Rust or Markdown sections for inspiration.
If your spare time budget is too short for this work, I could do this myself after we merge your MR. What you did is already a great contribution, and I am happy to merge it without you also having to do the LLRs.
9fa6e94 to
fe20de6
Compare
|
Thanks! I've written some L3 draft requirements for the image upload. I couldn't find, however, good parent requirements in L1/L2, so I left these unlinked. While writing them, I also realised that my implementation was incomplete in many edge cases. Therefore:
The implementation is now more complete, but you may need to re-review some of these areas. Please feel free to adapt or rewrite the L3 requirements to better align with the project’s guidelines. |
|
Thanks @thseiler for the extra effort. I will review your work very soon, and I did not even expect that you would uncover some incompleteness along the way 👍 The truth is that this recent L3 introduction is really based on common sense and the understanding that we needed a clear layer closest to the implementation. All precise linking and best practices for good L2-L3 traces are still a topic of exploration for me. This part:
I will handle myself and figure out what we actually want there 👍 |
|
I would also like to mention this: I recently looked into how the Confluence API works, and it turns out that Confluence does not really manage outdated assets when they are removed from pages where they were previously uploaded. If you download a page via the Confluence API, especially an old 'veteran' page with a lot of content, experience shows that all attachments for that page include both current and outdated assets that are no longer in use. It looks like the way you handle this with the |
|
I have pushed to a separate branch two integration tests: The test The test
Do you have an idea how to make your branch to support this case? Should we introduce a config option |
e37046a to
0ca5404
Compare
|
Many thanks for these test cases and for fixing the CI issues. I think I have found a possible solution: We can associate a top-level I realised that we don't need to support image uploads for nodes parsed from source code, since these are inherently read-only (autogen). I've now added an error message for when a user tries to upload an image to an autogenerated node, along with a test case to cover it. To support image upload even in projects that don't use MIDs, I also implemented using the UID as a fallback. A small sidenote: While working on this PR, I noticed that the UI allows users to modify autogen nodes, and the edits appear to work successfully (the in-memory model is updated), but the changes aren't persisted. (Ideally, the UI should indicate that edit functions are unavailable for such nodes. I hope to provide a separate PR in the coming days...) |
|
@thseiler I had a short vacation and was away from coding for some time. Please give me some time to review your new changes and re-evaluate the entire PR once more. I will also check your html2pdf4doc change soon. |
stanislaw
left a comment
There was a problem hiding this comment.
@thseiler I have integrated your smaller changes (the html2pdf4doc_python and Windows defender issues), feel free to rebase and remove those changes from your PR. Please also squash it to one commit on the way — it could help me review the next iterations locally.
Is there a chance you could join the office hours this week? I would like to discuss:
- If there is a way to avoid introducing a new @assets macro. Also, if we want to customize the behavior of handling paths differently if starting from
/(root) or without (relative). - If there are any implications of the overall algorithm when we develop StrictDoc to support multiple projects with multiple
strictdoc_config.pyfiles at the same time.
cc @haxtibal
| node_dict[field_name] = field.get_text_value() | ||
| raw_text = field.get_text_value() | ||
|
|
||
| if getattr(field, "multiline", False): |
There was a problem hiding this comment.
Is there a reason for using getattr here instead of field.multiline?
|
|
||
| [TEXT] | ||
| STATEMENT: >>> | ||
| .. image :: @assets/sandbox1.svg |
There was a problem hiding this comment.
Maybe it was me who introduced this typo but we probably should be doing .. image:: because that's how the directives usually look like.
| def _print_node_field(self, object_with_parts: SDocNodeField) -> str: | ||
| output = "" | ||
| prev_part = None | ||
| is_multiline = getattr(object_with_parts, "multiline", False) |
There was a problem hiding this comment.
Is there a reason for using getattr here instead of field.multiline?
| ) | ||
| ) | ||
|
|
||
| if document.autogen or getattr(requirement_node, "autogen", False): |
There was a problem hiding this comment.
Is there a reason for using getattr here instead of requirement_mode.autogen?
|
|
||
| node_has_permanent_mid = False | ||
| if requirement_node: | ||
| node_has_permanent_mid = getattr( |
There was a problem hiding this comment.
Is there a reason for using getattr here instead of requirement_mode.mid_permanent?
| else: | ||
| # Fallback to the UID, which we can only know after the node is created. | ||
| if requirement_node: | ||
| req_uid = getattr(requirement_node, "reserved_uid", None) |
There was a problem hiding this comment.
Is there a reason for using getattr here?
| if requirement_node: | ||
| node_has_permanent_mid = getattr( | ||
| requirement_node, "mid_permanent", False | ||
| ) or "MID" in getattr(requirement_node, "ordered_fields_lookup", {}) |
|
|
||
| def expand_assets_macro(text: str, assets_path: str) -> str: | ||
| """ | ||
| Expands the @assets/ macro to the _assets folder in the project root. |
There was a problem hiding this comment.
I keep thinking about this new @assets and am wondering if we could avoid introducing it as a new custom construct and use your path resolution algorithm from paths based on _assets without @.
I am slightly concerned about the portability of this solution. Yes, StrictDoc has custom things like LINK or ANCHOR, but maybe we could avoid creating this new macro @assets in this case.
5026bc9 to
a013075
Compare
a013075 to
48372ff
Compare
One area where StrictDoc is a bit unintuitive is the use of images:
This PR introduces an image upload mechanism to make things a little easier for new users.
When editing a requirement, it is now possible to:
While working on this, I realised that the current approach to placing images in the relative _assets folder is not ideal, especially when requirements are moved around. I will try to address this in a subsequent PR...