Skip to content

Commit

Permalink
Replace draft.js with tiptap
Browse files Browse the repository at this point in the history
Changes
---

There are a lot of changes! Replacing just draft.js was not too
difficult but it involved a couple of larger changes:

- Store and render comments at JSON. We're currently storing comments as
  markdown. The tiptap docs have [quite a bit to say about
  that](https://tiptap.dev/guide/output#not-an-option-markdown). The
  TL;DR is that tiptap will only output in JSON or HTML. JSON makes much
  more sense to me, since it's easier to do parsing/traversing and
  validation. Hopefully, we'll never have to manually do that, but if we
  ever *did*, I would much rather do it over JSON than HTML.
- Because we are now storing and displaying comments as JSON, we
  *always* render comments in the editor, even if they are not currently
  editable. This involves swapping around quite a lot of stuff inside of
  Transcript, but I think overall it's gotten easier to read/manage.

Other Changes
---

- Our types for comments are super interesting. I've changed things up a
  bit, pulling out an interface that Reply and Comment have in common,
  which I've called `Remark`. It's not used that often, but it's nice to
  have a shorthand.
- `PendingComment` and `PendingReply` are now just type aliases to
  `Partial<Comment>` and `Partial<Reply>`. I think this makes sense? So
  all fields are valid, but all of them also might not be there.
- Sort comments first by their video time, but then by the time they
  were created. When you have multiple comments at the same pause,
  replying to them can cause their order to change in the UI. This is a
  bit of an edge case but adding a stable tie-break to the sort avoids
  it.
- Add `tiptap` and some extensions. I have not added mention support
  completely, although I did POC it locally and it was good, though
  needed some design help.
- Remove all `Draft.js` code.
- Try to reduce the complexity in `Transcript` rendering. I am 95% sure that I could have taken this further, but this has been a quagmire of a change for two tricky reasons which I'll mention below, so I'm leaving this for another time.

Places I Got Stuck
---
- This is a silly one, but it was really hard to get `tiptap` to submit
  on hitting `Enter`. The docs about [overriding
  shortcuts](https://tiptap.dev/guide/output#not-an-option-markdown) did
  not work at all for me, nor did the answer to this
  [issue](ueberdosis/tiptap#207) which has a
  bunch of thumbsups. This makes me think that something changed in
  tiptap recently, because I had to do this:

```
Extension.create({
  name: "myCustomPlugin",
  addKeyboardShortcuts() {
    return {
      Enter: ({ editor }) => {
        //override code goes here
        return true;
      },
    };
  },
}),
```

This is *similar* to the docs, and to that issue answer, but it's just a
*bit* different, and for some reason this works and the others did not
\*shrug\*.

- Apollo optimistic updates failed silently. I have still not totally
  traced down the cause of this, but I'm *fairly* sure that it was
  caused by the `update` entry of a mutation that was writing an object
  to the Apollo cache without correctly setting all of the required
  fields and `__typename`. Again, maybe with more time I could get a
  more satisfying explanation, but for now I would rather just get this
  out.

---

Closes https://github.com/RecordReplay/devtools/issues/3735
  • Loading branch information
jcmorrow committed Oct 15, 2021
1 parent 3b4bc59 commit 78b27d8
Show file tree
Hide file tree
Showing 23 changed files with 1,323 additions and 1,414 deletions.
1,631 changes: 1,069 additions & 562 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
"dependencies": {
"@apollo/client": "^3.1.5",
"@auth0/auth0-react": "^1.2.0",
"@draft-js-plugins/editor": "^4.1.0",
"@draft-js-plugins/emoji": "^4.2.0",
"@draft-js-plugins/mention": "^4.3.1",
"@headlessui/react": "^1.4.1",
"@heroicons/react": "^1.0.1",
"@recordreplay/playwright": "^1.10.3",
Expand All @@ -28,6 +25,10 @@
"@stripe/react-stripe-js": "^1.4.1",
"@stripe/stripe-js": "^1.17.1",
"@tailwindcss/forms": "^0.3.2",
"@tiptap/extension-mention": "^2.0.0-beta.75",
"@tiptap/extension-placeholder": "^2.0.0-beta.33",
"@tiptap/react": "^2.0.0-beta.81",
"@tiptap/starter-kit": "^2.0.0-beta.123",
"apollo-link-http": "^1.5.17",
"babel-plugin-add-react-displayname": "0.0.5",
"base64-arraybuffer": "^0.2.0",
Expand All @@ -38,7 +39,6 @@
"debug": "^4.2.0",
"devtools-sprintf-js": "^1.0.3",
"dotenv": "^10.0.0",
"draft-js": "^0.11.7",
"escape-html": "^1.0.3",
"fontfaceobserver": "^2.1.0",
"graphql": "^15.3.0",
Expand Down Expand Up @@ -89,7 +89,6 @@
"@babel/preset-typescript": "^7.14.5",
"@babel/types": "^7.14.8",
"@types/classnames": "^2.2.11",
"@types/draft-js": "^0.10.44",
"@types/escape-html": "^1.0.1",
"@types/lodash": "^4.14.168",
"@types/logrocket-react": "^3.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ function PanelSummary({
e.stopPropagation();

if (pausedOnHit) {
console.log("createFrameComment", currentTime, executionPoint, breakpoint);
createFrameComment(currentTime, executionPoint, null, breakpoint);
} else {
console.log("createFloatingCodeComment", currentTime, executionPoint, breakpoint);
createFloatingCodeComment(currentTime, executionPoint, breakpoint);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class Message extends Component {
this.onViewSourceInDebugger({ ...frame, url: frame.source });
};
let handleAddComment = () => {
dispatch(actions.createComment(executionPointTime, executionPoint, null, true, frame));
dispatch(actions.createComment(executionPointTime, executionPoint, undefined, true, frame));
};

if (BigInt(executionPoint) > BigInt(pausedExecutionPoint)) {
Expand Down
78 changes: 26 additions & 52 deletions src/ui/actions/comments.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Action } from "redux";
import { selectors } from "ui/reducers";
import { actions } from "ui/actions";
import { PendingComment, Event, Comment, Reply, SourceLocation } from "ui/state/comments";
import { PendingComment, Comment, Reply, SourceLocation } from "ui/state/comments";
import { UIThunkAction } from ".";
import { ThreadFront } from "protocol/thread";
import { setSelectedPrimaryPanel } from "./app";
import escapeHtml from "escape-html";
import { waitForTime } from "protocol/utils";
import { getPendingComment } from "ui/reducers/comments";
import { PENDING_COMMENT_ID } from "ui/reducers/comments";
const { getFilenameFromURL } = require("devtools/client/debugger/src/utils/sources-tree/getURL");
const { getTextAtLocation } = require("devtools/client/debugger/src/reducers/sources");
const { findClosestFunction } = require("devtools/client/debugger/src/utils/ast");
Expand Down Expand Up @@ -61,20 +61,24 @@ export function createComment(
): UIThunkAction {
return async ({ dispatch }) => {
const labels = sourceLocation ? await dispatch(createLabels(sourceLocation)) : undefined;
const primaryLabel = labels?.primary || null;
const secondaryLabel = labels?.secondary || null;
const primaryLabel = labels?.primary;
const secondaryLabel = labels?.secondary;

const pendingComment: PendingComment = {
type: "new_comment",
comment: {
content: "",
time,
createdAt: new Date().toISOString(),
hasFrames,
id: PENDING_COMMENT_ID,
point,
position,
primaryLabel,
replies: [],
secondaryLabel,
hasFrames,
sourceLocation,
time,
updatedAt: new Date().toISOString(),
},
};

Expand All @@ -92,7 +96,7 @@ export function createFrameComment(
return async ({ dispatch }) => {
const sourceLocation =
breakpoint?.location || (await getCurrentPauseSourceLocationWithTimeout());
dispatch(createComment(time, point, position, true /* hasFrames */, sourceLocation || null));
dispatch(createComment(time, point, position, true, sourceLocation || null));
};
}

Expand All @@ -107,20 +111,7 @@ export function createFloatingCodeComment(
): UIThunkAction {
return async ({ dispatch }) => {
const { location: sourceLocation } = breakpoint;
dispatch(createComment(time, point, null, false /* hasFrames */, sourceLocation || null));
};
}

export function createNoFrameComment(
time: number,
point: string,
position: { x: number; y: number } | null,
breakpoint?: any
): UIThunkAction {
return async ({ dispatch }) => {
const sourceLocation =
breakpoint?.location || (await getCurrentPauseSourceLocationWithTimeout());
dispatch(createComment(time, point, position, false /* hasFrames */, sourceLocation || null));
dispatch(createComment(time, point, null, false, sourceLocation || null));
};
}

Expand Down Expand Up @@ -160,72 +151,55 @@ export function createLabels(sourceLocation: {
};
}

export function editItem(item: Comment | Reply): UIThunkAction {
export function editItem(item: Reply | Comment): UIThunkAction {
return async ({ dispatch }) => {
const { point, time, hasFrames } = item;

dispatch(seekToComment(item));

if (!("replies" in item)) {
const { content, sourceLocation, parentId, position, id } = item;

// Editing a reply.
const pendingComment: PendingComment = {
comment: item,
type: "edit_reply",
comment: { content, time, point, hasFrames, sourceLocation, parentId, position, id },
};
dispatch(setPendingComment(pendingComment));
} else {
const { content, primaryLabel, secondaryLabel, sourceLocation, id, position } = item;

// Editing a comment.
const pendingComment: PendingComment = {
comment: item,
type: "edit_comment",
comment: {
content,
primaryLabel,
secondaryLabel,
time,
point,
hasFrames,
sourceLocation,
id,
position,
},
};
dispatch(setPendingComment(pendingComment));
}
};
}

export function seekToComment(
item: Comment | Reply | Event | PendingComment["comment"]
): UIThunkAction {
export function seekToComment(item: Comment | Reply | PendingComment["comment"]): UIThunkAction {
return ({ dispatch, getState }) => {
dispatch(clearPendingComment());

let cx = selectors.getThreadContext(getState());
const hasFrames = "hasFrames" in item ? item.hasFrames : false;
dispatch(actions.seek(item.point, item.time, hasFrames));
if ("sourceLocation" in item && item.sourceLocation) {
dispatch(actions.seek(item.point, item.time, item.hasFrames));
if (item.sourceLocation) {
cx = selectors.getThreadContext(getState());
dispatch(actions.selectLocation(cx, item.sourceLocation));
}
};
}

export function replyToComment(comment: Comment): UIThunkAction {
export function replyToComment(parentId: string, point: string, time: number): UIThunkAction {
return ({ dispatch }) => {
const { time, point, hasFrames, id } = comment;
const pendingComment: PendingComment = {
type: "new_reply",
comment: {
content: "",
time,
point,
hasFrames,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
id: PENDING_COMMENT_ID,
hasFrames: false,
sourceLocation: null,
parentId: id,
parentId,
point,
time,
},
};

Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/Comments/CommentMarker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class CommentMarker extends React.Component<CommentMarkerProps> {
return null;
}

if (comment.time > zoomRegion.endTime) {
if (!comment.time || comment.time > zoomRegion.endTime) {
return null;
}

Expand Down
Loading

1 comment on commit 78b27d8

@vercel
Copy link

@vercel vercel bot commented on 78b27d8 Oct 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.