Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 6 additions & 4 deletions modules/study/src/main/StudyApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -468,15 +468,17 @@ final class StudyApi(
doSetClock(Study.WithChapter(study, c), Position(c, UciPath.root).ref, clock)(who)
yield sendTo(study.id)(_.setTags(chapter.id, chapter.tags, who))

def setComment(studyId: StudyId, position: Position.Ref, text: CommentStr)(who: Who) =
def setComment(studyId: StudyId, position: Position.Ref, commentId: Option[Comment.Id], text: CommentStr)(
who: Who
) =
sequenceStudyWithChapter(studyId, position.chapterId):
case Study.WithChapter(study, chapter) =>
Contribute(who.u, study):
lightUserApi
.async(who.u)
.flatMapz: author =>
val comment = Comment(
id = Comment.Id.make,
id = commentId.getOrElse(Comment.Id.make),
text = text,
by = Comment.Author.User(author.id, author.titleName)
)
Expand All @@ -486,8 +488,8 @@ final class StudyApi(
position.chapter.setComment(comment, position.path) match
case Some(newChapter) =>
newChapter.root.nodeAt(position.path).so { node =>
node.comments.findBy(comment.by).so { c =>
for _ <- chapterRepo.setComments(node.comments.filterEmpty)(newChapter, position.path)
node.comments.findByIdAndAuthor(comment.id, comment.by).so { c =>
for _ <- chapterRepo.setComments(node.comments.set(c))(newChapter, position.path)
yield
sendTo(study.id)(_.setComment(position.ref, c, who))
studyRepo.updateNow(study)
Expand Down
3 changes: 2 additions & 1 deletion modules/study/src/main/StudySocket.scala
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ final private class StudySocket(
(o \ "d" \ "text")
.asOpt[String]
.foreach: text =>
applyWho(api.setComment(studyId, position.ref, Comment.sanitize(text)))
val commentId = (o \ "d" \ "id").asOpt[String].map(Comment.Id.apply)
applyWho(api.setComment(studyId, position.ref, commentId, Comment.sanitize(text)))

case "deleteComment" =>
reading[AtPosition](o): position =>
Expand Down
4 changes: 2 additions & 2 deletions modules/study/src/test/StudyIntegrationTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,14 @@ object Fixtures:
""".trim

val pgn4 =
"1. e4 e6 2. d4 d5 3. Nc3 (3. exd5 exd5!! $15 { We have French exchange, the most exciting opening ever } 4. Bd3) (3. e5 c5 { French Defence: Advance Variation, another better position for Black } { [%cal Gc5d4,Gh2h4] }) 3... dxe4 { 3. Nc3 is the main weapon of White, but it doesn't match for the powerful Rubinstein. White is screwed here } (3... Bb4) 4. Nxe4 Nd7"
"1. e4 e6 2. d4 d5 3. Nc3 (3. exd5 exd5!! $15 { We have French exchange, the most exciting opening ever } 4. Bd3) (3. e5 c5 { French Defence: Advance Variation, another better position for Black } { [%cal Gc5d4,Gh2h4] }) 3... dxe4 { 3. Nc3 is the main weapon of White, but it doesn't match for the powerful Rubinstein. White is scrwed } { 3. Nc3 is the main weapon of White, but it doesn't match for the powerful Rubinstein. White is screwed here } (3... Bb4) 4. Nxe4 Nd7"

val m5 = s"""$m4\n{"t":"clearAnnotations","d":"kMOZO15F"}"""
val pgn5 = "1. e4 e6 2. d4 d5 3. Nc3 (3. exd5 exd5 4. Bd3) (3. e5 c5) 3... dxe4 (3... Bb4) 4. Nxe4 Nd7"

val m6 = s"""$m4\n{"t":"clearVariations","d":"kMOZO15F"}"""
val pgn6 =
"1. e4 e6 2. d4 d5 3. Nc3 dxe4 { 3. Nc3 is the main weapon of White, but it doesn't match for the powerful Rubinstein. White is screwed here } 4. Nxe4 Nd7"
"1. e4 e6 2. d4 d5 3. Nc3 dxe4 { 3. Nc3 is the main weapon of White, but it doesn't match for the powerful Rubinstein. White is scrwed } { 3. Nc3 is the main weapon of White, but it doesn't match for the powerful Rubinstein. White is screwed here } 4. Nxe4 Nd7"

val ms = List(m0, m1, m2, m3, m4, m5, m6)
val ps = List(pgn0, pgn1, pgn2, pgn3, pgn4, pgn5, pgn6)
Expand Down
8 changes: 6 additions & 2 deletions modules/tree/src/main/tree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -479,11 +479,15 @@ object Node:
opaque type Comments = List[Comment]
object Comments extends TotalWrapper[Comments, List[Comment]]:
extension (a: Comments)
def findById(id: Comment.Id) = a.value.find(_.id == id)
def findBy(author: Comment.Author) = a.value.find(_.by.is(author))
def findByIdAndAuthor(id: Comment.Id, author: Comment.Author) =
a.value.find(c => c.id == id && c.by.is(author))
def set(comment: Comment): Comments =
if a.value.exists(_.by.is(comment.by)) then
if a.value.exists(c => c.by.is(comment.by) && c.id == comment.id) then
a.value.map:
case c if c.by.is(comment.by) => c.copy(text = comment.text, by = comment.by)
case c if c.by.is(comment.by) && c.id == comment.id =>
c.copy(text = comment.text, by = comment.by)
case c => c
else a.value :+ comment
def delete(commentId: Comment.Id): Comments = a.value.filterNot(_.id == commentId)
Expand Down
2 changes: 1 addition & 1 deletion ui/analyse/src/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface StudySocketSendParams {
promote: (d: ReqPosition & { toMainline: boolean }) => void;
forceVariation: (d: ReqPosition & { force: boolean }) => void;
shapes: (d: ReqPosition & { shapes: Tree.Shape[] }) => void;
setComment: (d: ReqPosition & { text: string }) => void;
setComment: (d: ReqPosition & { id?: string; text: string }) => void;
deleteComment: (d: ReqPosition & { id: string }) => void;
setGamebook: (d: ReqPosition & { gamebook: { deviation?: string; hint?: string } }) => void;
toggleGlyph: (d: ReqPosition & { id: number }) => void;
Expand Down
51 changes: 34 additions & 17 deletions ui/analyse/src/study/commentForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ export class CommentForm {
opening = prop(false);
constructor(readonly root: AnalyseCtrl) {}

submit = (text: string) => this.current() && this.doSubmit(text);
submit = (commentId: string, text: string) => this.current() && this.doSubmit(commentId, text);

doSubmit = throttle(500, (text: string) => {
doSubmit = throttle(500, (commentId: string, text: string) => {
const cur = this.current();
if (cur) this.root.study!.makeChange('setComment', { ch: cur.chapterId, path: cur.path, text });
if (cur)
this.root.study!.makeChange('setComment', { ch: cur.chapterId, path: cur.path, text, id: commentId });
});

start = (chapterId: string, path: Tree.Path, node: Tree.Node): void => {
Expand Down Expand Up @@ -51,13 +52,14 @@ export function view(root: AnalyseCtrl): VNode {
ctrl = study.commentForm,
current = ctrl.current();
if (!current) return viewDisabled(root, 'Select a move to comment');
const setupTextarea = (vnode: VNode, old?: VNode) => {

const setupTextarea = (vnode: VNode, comment?: any, old?: VNode) => {
const el = vnode.elm as HTMLInputElement;
const newKey = current.chapterId + current.path;
const newKey = current.chapterId + current.path + (comment ? comment.id : '');

if (old?.data!.path !== newKey) {
const mine = (current.node.comments || []).find(function (c) {
return isAuthorObj(c.by) && c.by.id && c.by.id === ctrl.root.opts.userId;
return isAuthorObj(c.by) && c.by.id && c.by.id === ctrl.root.opts.userId && c.id === comment!.id;
});
el.value = mine ? mine.text : '';
}
Expand All @@ -69,30 +71,45 @@ export function view(root: AnalyseCtrl): VNode {
}
};

return h(
'div.study__comments',
{ hook: onInsert(() => root.enableWiki(root.data.game.variant.key === 'standard')) },
[
currentComments(root, !study.members.canContribute()),
h('form.form3', [
h('textarea#comment-text.form-control', {
const commentTextareas = () => {
let comments = current.node.comments || [];
comments = comments.filter(comment => {
return isAuthorObj(comment.by) && comment.by.id === ctrl.root.opts.userId;
});
if (comments.length === 0) {
comments = [{ id: '', by: '', text: '' }];
}
return comments.map(comment =>
h('div.study__comment-edit', [
h('textarea.form-control', {
key: comment.id,
props: { value: comment.text },
hook: {
insert(vnode) {
setupTextarea(vnode);
setupTextarea(vnode, comment);
const el = vnode.elm as HTMLInputElement;
el.oninput = () => setTimeout(() => ctrl.submit(el.value), 50);
const heightStore = storage.make('study.comment.height');
el.oninput = () => setTimeout(() => ctrl.submit(comment.id, el.value), 50);
const heightStore = storage.make('study.comment.height.' + comment.id);
el.onmouseup = () => heightStore.set('' + el.offsetHeight);
el.style.height = parseInt(heightStore.get() || '80') + 'px';

$(el).on('keydown', e => {
if (e.code === 'Escape') el.blur();
});
},
postpatch: (old, vnode) => setupTextarea(vnode, old),
postpatch: (old, vnode) => setupTextarea(vnode, comment, old),
},
}),
]),
);
};

return h(
'div.study__comments',
{ hook: onInsert(() => root.enableWiki(root.data.game.variant.key === 'standard')) },
[
currentComments(root, !study.members.canContribute()),
h('form.form3', commentTextareas()),
h('div.analyse__wiki.study__wiki.force-ltr'),
],
);
Expand Down