diff --git a/README.md b/README.md index 2817325..b1ba8ca 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,9 @@ Here are the props you can pass to the MystEditor component: - `topbar` *(default: true)* - whether to show the topbar - `templateList` - path/url to a JSON file containing your document templates. For an example see `public/linkedtemplatelist.json`. - `transforms` - [custom transforms](#custom-transforms) +- `getAvatar` *(default: (login) => `https://secure.gravatar.com/avatar/${login}?s=30&d=identicon`)* - a function that returns the avatar for a given username +- `getUserUrl` *(default: (login) => `#`)* - a function that returns a url to a web page with a users profile + It is used when an avatar is clicked. - `collaboration` - options related to live collaboration: - `enabled` *(default: false)* - `wsUrl` *(example: ws://example:4444)* - url of the websocket server diff --git a/src/MystEditor.js b/src/MystEditor.js index 2821631..a51d2d5 100644 --- a/src/MystEditor.js +++ b/src/MystEditor.js @@ -135,6 +135,7 @@ const MystEditor = ({ transforms = [], // this will create a bogus random avatar when no specific getAvatar function is provided getAvatar = (login) => `https://secure.gravatar.com/avatar/${login}?s=30&d=identicon`, + getUserUrl = (login) => "#", backslashLineBreak = true, parent, syncScroll = false, @@ -147,10 +148,13 @@ const MystEditor = ({ const text = useText({ initialText, transforms, customRoles, preview, backslashLineBreak, parent }); const [alert, setAlert] = useState(null); - const [users, setUsers] = useReducer((_, currentUsers) => currentUsers.map((u) => ({ ...u, avatarUrl: getAvatar(u.login) })), []); + const [users, setUsers] = useReducer( + (_, currentUsers) => currentUsers.map((u) => ({ ...u, avatarUrl: getAvatar(u.login), userUrl: getUserUrl(u.login) })), + [], + ); const { provider, undoManager, ytext, ydoc, ready, error } = useCollaboration(collaboration); - const ycomments = useComments(ydoc, provider, getAvatar); + const ycomments = useComments(ydoc, provider, getAvatar, getUserUrl); const alertFor = (alertText, secs) => { setAlert(alertText); diff --git a/src/comments/ycomments.js b/src/comments/ycomments.js index b64eb41..e96d206 100644 --- a/src/comments/ycomments.js +++ b/src/comments/ycomments.js @@ -31,12 +31,13 @@ const newRandomCommentId = () => "comment-" + Math.random().toString().replace(" * into Y.Array are immutable (if they are not Y.js primitives). */ export class CommentLineAuthors { - constructor(/** @type {Y.Doc} */ ydoc, provider, getAvatar, commentId) { + constructor(/** @type {Y.Doc} */ ydoc, provider, getAvatar, getUserUrl, commentId) { this.user = provider.awareness.getLocalState().user; /** @type {Y.Array>} */ this.lineAuthors = ydoc.getArray(commentId + "/commentLineAuthors"); this.ydoc = ydoc; this.getAvatar = getAvatar; + this.getUserUrl = getUserUrl; this.commentId = commentId; } @@ -46,6 +47,7 @@ export class CommentLineAuthors { if (!authorData) return; authorData.avatar = this.getAvatar(authorData.name); + authorData.url = this.getUserUrl(authorData.name); return authorData; } @@ -269,10 +271,11 @@ export class YComments { * @param {Y.Doc} ydoc * @param {WebsocketProvider} provider */ - constructor(ydoc, provider, getAvatar) { + constructor(ydoc, provider, getAvatar, getUserUrl) { this.ydoc = ydoc; this.provider = provider; this.getAvatar = getAvatar; + this.getUserUrl = getUserUrl; /** @type {EditorView} The main codemirror instance */ this.mainCodeMirror = null; @@ -304,7 +307,7 @@ export class YComments { } lineAuthors(commentId) { - return new CommentLineAuthors(this.ydoc, this.provider, this.getAvatar, commentId); + return new CommentLineAuthors(this.ydoc, this.provider, this.getAvatar, this.getUserUrl, commentId); } positions() { diff --git a/src/components/Avatars.js b/src/components/Avatars.js index a211315..b73e655 100644 --- a/src/components/Avatars.js +++ b/src/components/Avatars.js @@ -34,8 +34,10 @@ const AvatarsWrapper = styled.div` } `; -export const Avatar = ({ login, color, avatarUrl }) => - html` `; +export const Avatar = ({ login, color, avatarUrl, userUrl }) => + html``; const AvatarPlaceholder = ({ n, usernames }) => html` <${AvatarsWrapper} title=${usernames}> diff --git a/src/components/ResolvedComment.js b/src/components/ResolvedComment.js index 7220c9f..6265e72 100644 --- a/src/components/ResolvedComment.js +++ b/src/components/ResolvedComment.js @@ -258,7 +258,7 @@ const ResolvedComment = ({ c, authors, ycomments, content }) => { <${CommentContainer} className="resolved-comment" color=${authors.get(1).color}> <${CommentTopbar}> <${FlexRow}> - <${Avatar} login=${authors.get(1).name} color=${authors.get(1).color} avatarUrl=${authors.get(1).avatar} /> + <${Avatar} login=${authors.get(1).name} color=${authors.get(1).color} avatarUrl=${authors.get(1).avatar} userUrl=${authors.get(1).url} /> <${CommentAuthor}>${authors.get(1).name} <${FlexRow}> diff --git a/src/hooks/useComments.js b/src/hooks/useComments.js index deffe2f..e47c38d 100644 --- a/src/hooks/useComments.js +++ b/src/hooks/useComments.js @@ -1,10 +1,10 @@ import { useMemo } from "preact/hooks"; import { YComments } from "../comments/ycomments"; -export default function useComments(ydoc, provider, getAvatar) { +export default function useComments(ydoc, provider, getAvatar, getUserUrl) { if (!ydoc || !provider) return null; return useMemo(() => { - const ycomments = new YComments(ydoc, provider, getAvatar); + const ycomments = new YComments(ydoc, provider, getAvatar, getUserUrl); window.myst_editor.ycomments = ycomments; return ycomments; }, []);