@@ -4,6 +4,8 @@ import EndpointChapters$authorVanity$workVanity from 'endpoint/chapters/$author_
44import EndpointHistoryBookmarks$authorVanity$workVanityDeleteFurthestRead from 'endpoint/history/bookmarks/$author_vanity/$work_vanity/delete/EndpointHistoryBookmarks$authorVanity$workVanityDeleteFurthestRead'
55import EndpointHistoryBookmarks$authorVanity$workVanityDeleteLastRead from 'endpoint/history/bookmarks/$author_vanity/$work_vanity/delete/EndpointHistoryBookmarks$authorVanity$workVanityDeleteLastRead'
66import EndpointHistoryWork$authorVanity$workVanityAdd from 'endpoint/history/work/$author_vanity/$work_vanity/EndpointHistoryWork$authorVanity$workVanityAdd'
7+ import EndpointReactionsWork$authorVanity$workVanityAdd from 'endpoint/reactions/work/$author_vanity/$work_vanity/EndpointReactionsWork$authorVanity$workVanityAdd'
8+ import EndpointReactionsWork$authorVanity$workVanityDelete from 'endpoint/reactions/work/$author_vanity/$work_vanity/EndpointReactionsWork$authorVanity$workVanityDelete'
79import EndpointWorks$authorVanity$workVanityGet from 'endpoint/works/$author_vanity/$work_vanity/EndpointWorks$authorVanity$workVanityGet'
810import Chapters from 'model/Chapters'
911import PagedListData from 'model/PagedListData'
@@ -12,6 +14,7 @@ import Tags from 'model/Tags'
1214import Works from 'model/Works'
1315import Component from 'ui/Component'
1416import ActionBlock from 'ui/component/ActionBlock'
17+ import AuthorLink from 'ui/component/AuthorLink'
1518import Chapter from 'ui/component/Chapter'
1619import type { CommentData } from 'ui/component/Comment'
1720import Comment from 'ui/component/Comment'
@@ -20,11 +23,14 @@ import CommentTree from 'ui/component/CommentTree'
2023import Block from 'ui/component/core/Block'
2124import Button from 'ui/component/core/Button'
2225import GradientText from 'ui/component/core/ext/GradientText'
26+ import Heading from 'ui/component/core/Heading'
27+ import Icon from 'ui/component/core/Icon'
2328import Link from 'ui/component/core/Link'
2429import Paginator from 'ui/component/core/Paginator'
2530import Placeholder from 'ui/component/core/Placeholder'
2631import Slot from 'ui/component/core/Slot'
2732import Tabinator , { Tab } from 'ui/component/core/Tabinator'
33+ import Reaction from 'ui/component/Reaction'
2834import Work from 'ui/component/Work'
2935import DynamicDestination from 'ui/utility/DynamicDestination'
3036import Viewport from 'ui/utility/Viewport'
@@ -316,23 +322,61 @@ export default ViewDefinition({
316322 . setAestheticLevel ( 4 )
317323 . text . use ( 'view/work/comments/title' )
318324 )
319- . tweak ( block => block . content . append ( Slot ( ) . use ( [ commentState , workData , ageRestricted ] , ( slot , thread , work , ageRestricted ) => {
325+ . tweak ( block => block . content . append ( Slot ( ) . use ( [ commentState , workData , ageRestricted , Session . Auth . author ] , ( slot , thread , work , ageRestricted , self ) => {
320326 if ( ! thread || ageRestricted )
321327 return
322328
329+ const MAX_RECOMMENDATIONS_DISPLAY = 8
330+ // work.recommendation_reactions_count = 35
331+ const ReactionRecommendations = ( ) => Component ( )
332+ . style ( 'view-type-work-comment-block-reactions' )
333+ . append ( ...work . recommendation_reactions
334+ . slice ( 0 , MAX_RECOMMENDATIONS_DISPLAY )
335+ . map ( reaction => {
336+ if ( reaction . author === self ?. vanity )
337+ return undefined
338+
339+ const author = workData . value . synopsis . mentions . find ( mention => mention . vanity === reaction . author )
340+ if ( ! author )
341+ return undefined
342+
343+ return reaction . author === self ?. vanity ? undefined
344+ : Component ( )
345+ . style ( 'view-type-work-comment-block-reactions-instance' )
346+ . append ( reaction . reaction_type === 'supporter_love'
347+ ? Icon ( 'supporter-heart' )
348+ . style ( 'view-type-chapter-block-supporter-reaction' )
349+ . and ( GradientText , 'heart-gradient' , '115deg' )
350+ . useGradient ( author . supporter ?. username_colours )
351+ : Icon ( 'heart' )
352+ )
353+ . append ( AuthorLink ( author )
354+ . style ( 'view-type-work-comment-block-reactions-author-link' )
355+ )
356+ } )
357+ )
358+ . append ( work . recommendation_reactions_count > MAX_RECOMMENDATIONS_DISPLAY && Component ( )
359+ . style ( 'view-type-work-comment-block-reactions-plus' )
360+ . text . use ( quilt => quilt [ 'view/work/comments/reactions/plus' ] ( work . recommendation_reactions_count - MAX_RECOMMENDATIONS_DISPLAY ) )
361+ )
362+
363+ if ( ! self )
364+ ReactionRecommendations ( )
365+ . appendTo ( slot )
366+
323367 const isOwnWork = Session . Auth . loggedInAs ( slot , thread . threadAuthor )
324368 const commentsRenderDefinition : CommentTreeRenderDefinition = {
325369 simpleTimestamps : true ,
326370 onCommentsUpdate ( comments ) {
327- const ownRecommendation = comments . find ( comment => comment . author === Session . Auth . author . value ?. vanity && ! comment . edit )
371+ const ownRecommendation = comments . find ( comment => comment . author === self ?. vanity && ! comment . edit )
328372 if ( ! work . recommendation && ownRecommendation ) {
329373 work . recommendation = ownRecommendation
330374 workData . emit ( )
331375 }
332376 } ,
333377 shouldSkipComment ( data ) {
334378 return true
335- && data . author === Session . Auth . author . value ?. vanity
379+ && data . author === self ?. vanity
336380 && ! data . edit
337381 } ,
338382 onRenderComment ( comment , data ) {
@@ -346,6 +390,7 @@ export default ViewDefinition({
346390
347391 comment . editor
348392 . setMinimal ( tabletMode . falsy )
393+ . placeholder . use ( 'view/work/comments/editor/placeholder' )
349394 . hint . use ( )
350395
351396 if ( data . comment_id )
@@ -354,41 +399,93 @@ export default ViewDefinition({
354399
355400 // this is the new comment editor (only possible on root)
356401 // dynamically replace the editor with hints or the existing recommendation if applicable
357- const existingRecommendation = work . recommendation as CommentData | undefined
358- const canRecommend = ! existingRecommendation && ! work . bookmarks ?. can_recommend ? State ( false )
402+ const existingRecommendationComment = work . recommendation as CommentData | undefined
403+ const canRecommend = ! existingRecommendationComment && ! work . bookmarks ?. can_recommend ? State ( false )
359404 : isOwnWork . falsy
360- const noExistingRecommendation = State ( ! existingRecommendation )
361- const showCommentEditor = State . Every ( comment , canRecommend , noExistingRecommendation )
362- const showCommentHint = State . Every ( comment , canRecommend . falsy , isOwnWork . falsy , noExistingRecommendation )
405+ const noExistingRecommendationComment = State ( ! existingRecommendationComment )
363406
364- comment . author ?. text . use ( 'view/work/comments/action/add/label' )
365- . style ( 'view-type-work-comment-editor-author-hint' )
407+ const reacting = State ( false )
408+ const reacted = State ( ! ! work . recommendation_reacted )
409+
410+ const showCommentEditor = State . Every ( comment , canRecommend , noExistingRecommendationComment , reacted )
411+ const showCommentHint = State . Every ( comment , canRecommend . falsy , isOwnWork . falsy , noExistingRecommendationComment , reacted . falsy )
412+
413+ comment . author ?. remove ( )
366414 comment . onRooted ( ( ) => {
367415 const parent = comment . parent !
368416
417+ ReactionRecommendations ( )
418+ . prependTo ( parent )
419+
420+ const hasRecommended = State . Some ( comment , reacted , noExistingRecommendationComment . falsy )
421+ const workHasOtherRecommendationReactions = hasRecommended . map ( comment , hasRecommended => work . recommendation_reactions_count > + hasRecommended )
422+ Heading ( )
423+ . setAestheticLevel ( 6 )
424+ . text . use ( 'view/work/comments/other-recommendations-title' )
425+ . prependToWhen ( workHasOtherRecommendationReactions , parent )
426+
369427 // show comment editor only when able to recommend & no existing recommendation
370428 comment . prependToWhen ( showCommentEditor , parent )
371429
430+ Component ( )
431+ . style ( 'view-type-work-comment-hint' , 'view-type-work-comment-hint--borderless' )
432+ . append ( ( self ?. supporter ?. tier
433+ ? Reaction ( 'supporter_love' , 0 , reacted , reacting )
434+ . and ( GradientText , 'heart-gradient' , '115deg' )
435+ . useGradient ( Session . Auth . author . map ( slot , author => author ?. supporter ?. username_colours ) )
436+ : Reaction ( 'love' , 0 , reacted , reacting )
437+ )
438+ . style ( 'view-type-work-comment-hint-reaction-button' )
439+ . event . subscribe ( 'click' , async ( ) => {
440+ if ( reacting . value )
441+ return
442+
443+ reacting . value = true
444+ const endpoint = reacted . value
445+ ? EndpointReactionsWork$authorVanity$workVanityDelete
446+ : EndpointReactionsWork$authorVanity$workVanityAdd
447+
448+ const response = await endpoint . query ( { params : Works . reference ( work ) } )
449+ if ( toast . handleError ( response ) ) {
450+ reacting . value = false
451+ return
452+ }
453+
454+ reacted . value = ! reacted . value
455+ reacting . value = false
456+ } )
457+ )
458+ . append ( Link ( undefined )
459+ . and ( GradientText )
460+ . useGradient ( self ?. supporter ?. username_colours )
461+ . style ( 'author-link' )
462+ . text . bind ( reacted . map ( comment , reacted => quilt => quilt [ reacted
463+ ? 'view/work/comments/action/added/label'
464+ : 'view/work/comments/action/add/label'
465+ ] ( ) ) )
466+ )
467+ . prependToWhen ( State . Every ( comment , showCommentHint . falsy , noExistingRecommendationComment ) , parent )
468+
372469 // show hint when unable to recommend & no existing recommendation
373470 Link ( undefined )
374471 . and ( GradientText )
375- . useGradient ( Session . Auth . author . value ?. supporter ?. username_colours )
472+ . useGradient ( self ?. supporter ?. username_colours )
376473 . style ( 'view-type-work-comment-hint' , 'author-link' )
377474 . text . use ( 'view/work/comments/action/no-add/label' )
378475 . prependToWhen ( showCommentHint , parent )
379476
380477 // otherwise show existing recommendation
381- if ( existingRecommendation ) {
382- const existingRecommendationState = State ( [ existingRecommendation ] )
478+ if ( existingRecommendationComment ) {
479+ const existingRecommendationState = State ( [ existingRecommendationComment ] )
383480 existingRecommendationState . subscribeManual ( ( [ existingRecommendation ] ) => {
384481 if ( ! existingRecommendation ) {
385482 work . recommendation = undefined
386483 workData . emit ( )
387484 }
388485 } )
389486 Comment (
390- { threadAuthor : thread . threadAuthor , comments : existingRecommendationState , authors : State ( existingRecommendation . body ?. mentions ?? [ ] ) } ,
391- existingRecommendation ,
487+ { threadAuthor : thread . threadAuthor , comments : existingRecommendationState , authors : State ( existingRecommendationComment . body ?. mentions ?? [ ] ) } ,
488+ existingRecommendationComment ,
392489 undefined ,
393490 commentsRenderDefinition ,
394491 )
@@ -397,11 +494,17 @@ export default ViewDefinition({
397494 } )
398495 } ,
399496 onNoComments ( slot ) {
400- Placeholder ( )
401- . style ( 'view-type-work-comment-placeholder' , 'view-type-work-comment-placeholder--empty' )
402- . style . bind ( isOwnWork , 'view-type-work-comment-placeholder--empty--is-own-work' )
403- . text . use ( 'view/work/comments/content/empty' )
404- . appendTo ( slot )
497+ if ( ! work . recommendation_reactions . length )
498+ Placeholder ( )
499+ . style ( 'view-type-work-comment-placeholder' , 'view-type-work-comment-placeholder--empty' )
500+ . style . bind ( isOwnWork , 'view-type-work-comment-placeholder--empty--is-own-work' )
501+ . text . use ( 'view/work/comments/content/empty' )
502+ . appendTo ( slot )
503+ else
504+ Placeholder ( )
505+ . style ( 'view-type-work-comment-placeholder' , 'view-type-work-comment-placeholder--empty' , 'view-type-work-comment-placeholder--end' )
506+ . text . use ( 'view/work/comments/content/end' )
507+ . appendTo ( slot )
405508 } ,
406509 onCommentsEnd ( slot ) {
407510 Placeholder ( )
0 commit comments