Note: All of this functionality is hidden behind a hidden, default false, site setting called `enable_bookmarks_with_reminders`. Also, any feedback on Ember code would be greatly appreciated! This is part 1 of the bookmark improvements. The next PR will address the backend logic to send reminder notifications for bookmarked posts to users. This PR adds the following functionality: * We are adding a new `bookmarks` table and `Bookmark` model to make the bookmarks a first-class citizen and to allow attaching reminders to them. * Posts now have a new button in their actions menu that has the icon of an actual book * Clicking the button opens the new bookmark modal. * Both name and the reminder type are optional. * If you close the modal without doing anything, the bookmark is saved with no reminder. * If you click the Cancel button, no bookmark is saved at all. * All of the reminder type tiles are dynamic and the times they show will be based on your user timezone set in your profile (this should already be set for you). * If for some reason a user does not have their timezone set they will not be able to set a reminder, but they will still be able to create a bookmark. * A bookmark can be deleted by clicking on the book icon again which will be red if the post is bookmarked. This PR does NOT do anything to migrate or change existing bookmarks in the form of `PostActions`, the two features live side-by-side here. Also this does nothing to the topic bookmarking.
376 lines
17 KiB
Handlebars
376 lines
17 KiB
Handlebars
{{#discourse-topic multiSelect=multiSelect enteredAt=enteredAt topic=model hasScrolled=hasScrolled}}
|
|
{{#if model}}
|
|
{{add-category-tag-classes category=model.category tags=model.tags}}
|
|
<div class="container">
|
|
{{discourse-banner user=currentUser banner=site.banner overlay=hasScrolled hide=model.errorLoading}}
|
|
</div>
|
|
{{/if}}
|
|
|
|
{{#if showSharedDraftControls}}
|
|
{{shared-draft-controls topic=model}}
|
|
{{/if}}
|
|
|
|
{{plugin-outlet name="topic-above-post-stream" args=(hash model=model)}}
|
|
|
|
{{#if model.postStream.loaded}}
|
|
{{#if model.postStream.firstPostPresent}}
|
|
{{#topic-title cancelled=(action "cancelEditingTopic") save=(action "finishedEditingTopic") model=model}}
|
|
{{#if editingTopic}}
|
|
<div class="edit-topic-title">
|
|
{{#if model.isPrivateMessage}}
|
|
<span class="private-message-glyph">{{d-icon "envelope"}}</span>
|
|
{{/if}}
|
|
{{text-field id="edit-title" value=buffered.title maxlength=siteSettings.max_topic_title_length autofocus="true"}}
|
|
|
|
{{#if showCategoryChooser}}
|
|
{{category-chooser
|
|
class="small"
|
|
value=(unbound buffered.category_id)
|
|
onSelectAny=(action "topicCategoryChanged")}}
|
|
{{/if}}
|
|
|
|
{{#if canEditTags}}
|
|
{{mini-tag-chooser
|
|
filterable=true
|
|
tags=(unbound buffered.tags)
|
|
categoryId=(unbound buffered.category_id)
|
|
onChangeTags=(action "topicTagsChanged")}}
|
|
{{/if}}
|
|
|
|
{{plugin-outlet name="edit-topic" args=(hash model=model buffered=buffered)}}
|
|
|
|
<div class="edit-controls">
|
|
{{d-button action=(action "finishedEditingTopic") class="btn-primary submit-edit" icon="check"}}
|
|
{{d-button action=(action "cancelEditingTopic") class="btn-default cancel-edit" icon="times"}}
|
|
|
|
{{#if canRemoveTopicFeaturedLink}}
|
|
<a href {{action "removeFeaturedLink"}} class="remove-featured-link" title="{{i18n "composer.remove_featured_link"}}">
|
|
{{d-icon "times-circle"}}
|
|
{{featuredLinkDomain}}
|
|
</a>
|
|
{{/if}}
|
|
</div>
|
|
</div>
|
|
|
|
{{else}}
|
|
<h1 data-topic-id="{{unbound model.id}}">
|
|
{{#unless model.is_warning}}
|
|
{{#if siteSettings.enable_personal_messages}}
|
|
{{#if model.isPrivateMessage}}
|
|
<a href={{pmPath}} title="{{i18n 'topic_statuses.personal_message.title'}}" aria-label={{i18n 'user.messages.inbox'}}>
|
|
<span class="private-message-glyph">{{d-icon "envelope"}}</span>
|
|
</a>
|
|
{{/if}}
|
|
{{else}}
|
|
{{#if model.isPrivateMessage}}
|
|
<span class="private-message-glyph">{{d-icon "envelope"}}</span>
|
|
{{/if}}
|
|
{{/if}}
|
|
{{/unless}}
|
|
|
|
{{#if model.details.loaded}}
|
|
{{topic-status topic=model}}
|
|
<a href="{{unbound model.url}}" {{action "jumpTop"}} class="fancy-title">
|
|
{{{model.fancyTitle}}}
|
|
</a>
|
|
{{/if}}
|
|
|
|
{{#if model.details.can_edit}}
|
|
<a href {{action "editTopic"}} class="edit-topic" title="{{i18n "edit"}}">{{d-icon "pencil-alt"}}</a>
|
|
{{/if}}
|
|
</h1>
|
|
|
|
{{topic-category topic=model class="topic-category"}}
|
|
{{/if}}
|
|
{{/topic-title}}
|
|
{{/if}}
|
|
|
|
|
|
<div class="container posts">
|
|
<div class='selected-posts {{unless multiSelect 'hidden'}}'>
|
|
{{partial "selected-posts"}}
|
|
</div>
|
|
|
|
{{#topic-navigation topic=model jumpToDate=(action "jumpToDate") jumpToIndex=(action "jumpToIndex") as |info|}}
|
|
{{#if info.renderTimeline}}
|
|
{{#if info.renderAdminMenuButton}}
|
|
{{topic-admin-menu-button
|
|
topic=model
|
|
fixed="true"
|
|
toggleMultiSelect=(action "toggleMultiSelect")
|
|
deleteTopic=(action "deleteTopic")
|
|
recoverTopic=(action "recoverTopic")
|
|
toggleClosed=(action "toggleClosed")
|
|
toggleArchived=(action "toggleArchived")
|
|
toggleVisibility=(action "toggleVisibility")
|
|
showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
|
|
showFeatureTopic=(route-action "showFeatureTopic")
|
|
showChangeTimestamp=(route-action "showChangeTimestamp")
|
|
resetBumpDate=(action "resetBumpDate")
|
|
convertToPublicTopic=(action "convertToPublicTopic")
|
|
convertToPrivateMessage=(action "convertToPrivateMessage")}}
|
|
{{/if}}
|
|
|
|
{{topic-timeline
|
|
topic=model
|
|
notificationLevel=model.details.notification_level
|
|
prevEvent=info.prevEvent
|
|
fullscreen=info.topicProgressExpanded
|
|
enteredIndex=enteredIndex
|
|
loading=model.postStream.loading
|
|
jumpToPost=(action "jumpToPost")
|
|
jumpTop=(action "jumpTop")
|
|
jumpBottom=(action "jumpBottom")
|
|
jumpEnd=(action "jumpEnd")
|
|
jumpToPostPrompt=(action "jumpToPostPrompt")
|
|
jumpToIndex=(action "jumpToIndex")
|
|
replyToPost=(action "replyToPost")
|
|
toggleMultiSelect=(action "toggleMultiSelect")
|
|
deleteTopic=(action "deleteTopic")
|
|
recoverTopic=(action "recoverTopic")
|
|
toggleClosed=(action "toggleClosed")
|
|
toggleArchived=(action "toggleArchived")
|
|
toggleVisibility=(action "toggleVisibility")
|
|
showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
|
|
showFeatureTopic=(route-action "showFeatureTopic")
|
|
showChangeTimestamp=(route-action "showChangeTimestamp")
|
|
resetBumpDate=(action "resetBumpDate")
|
|
convertToPublicTopic=(action "convertToPublicTopic")
|
|
convertToPrivateMessage=(action "convertToPrivateMessage")}}
|
|
{{else}}
|
|
{{#topic-progress
|
|
prevEvent=info.prevEvent
|
|
topic=model
|
|
expanded=info.topicProgressExpanded
|
|
jumpToPost=(action "jumpToPost")}}
|
|
{{plugin-outlet name="before-topic-progress" args=(hash model=model jumpToPost=(action "jumpToPost"))}}
|
|
{{#if info.renderAdminMenuButton}}
|
|
{{topic-admin-menu-button
|
|
topic=model
|
|
openUpwards="true"
|
|
rightSide="true"
|
|
toggleMultiSelect=(action "toggleMultiSelect")
|
|
deleteTopic=(action "deleteTopic")
|
|
recoverTopic=(action "recoverTopic")
|
|
toggleClosed=(action "toggleClosed")
|
|
toggleArchived=(action "toggleArchived")
|
|
toggleVisibility=(action "toggleVisibility")
|
|
showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
|
|
showFeatureTopic=(route-action "showFeatureTopic")
|
|
showChangeTimestamp=(route-action "showChangeTimestamp")
|
|
resetBumpDate=(action "resetBumpDate")
|
|
convertToPublicTopic=(action "convertToPublicTopic")
|
|
convertToPrivateMessage=(action "convertToPrivateMessage")}}
|
|
{{/if}}
|
|
{{/topic-progress}}
|
|
{{/if}}
|
|
{{/topic-navigation}}
|
|
|
|
<div class="row">
|
|
<section class="topic-area" id="topic" data-topic-id="{{unbound model.id}}">
|
|
|
|
<div class="posts-wrapper">
|
|
{{conditional-loading-spinner condition=model.postStream.loadingAbove}}
|
|
|
|
{{plugin-outlet name="topic-above-posts" args=(hash model=model)}}
|
|
|
|
{{#unless model.postStream.loadingFilter}}
|
|
{{scrolling-post-stream
|
|
posts=postsToRender
|
|
canCreatePost=model.details.can_create_post
|
|
multiSelect=multiSelect
|
|
selectedPostsCount=selectedPostsCount
|
|
selectedQuery=selectedQuery
|
|
gaps=model.postStream.gaps
|
|
showReadIndicator=model.show_read_indicator
|
|
showFlags=(action "showPostFlags")
|
|
editPost=(action "editPost")
|
|
showHistory=(route-action "showHistory")
|
|
showLogin=(route-action "showLogin")
|
|
showRawEmail=(route-action "showRawEmail")
|
|
deletePost=(action "deletePost")
|
|
recoverPost=(action "recoverPost")
|
|
expandHidden=(action "expandHidden")
|
|
newTopicAction=(action "replyAsNewTopic")
|
|
toggleBookmark=(action "toggleBookmark")
|
|
toggleBookmarkWithReminder=(action "toggleBookmarkWithReminder")
|
|
togglePostType=(action "togglePostType")
|
|
rebakePost=(action "rebakePost")
|
|
changePostOwner=(action "changePostOwner")
|
|
grantBadge=(action "grantBadge")
|
|
addNotice=(action "addNotice")
|
|
removeNotice=(action "removeNotice")
|
|
lockPost=(action "lockPost")
|
|
unlockPost=(action "unlockPost")
|
|
unhidePost=(action "unhidePost")
|
|
replyToPost=(action "replyToPost")
|
|
toggleWiki=(action "toggleWiki")
|
|
toggleSummary=(action "toggleSummary")
|
|
removeAllowedUser=(action "removeAllowedUser")
|
|
removeAllowedGroup=(action "removeAllowedGroup")
|
|
topVisibleChanged=(action "topVisibleChanged")
|
|
currentPostChanged=(action "currentPostChanged")
|
|
currentPostScrolled=(action "currentPostScrolled")
|
|
bottomVisibleChanged=(action "bottomVisibleChanged")
|
|
togglePostSelection=(action "togglePostSelection")
|
|
selectReplies=(action "selectReplies")
|
|
selectBelow=(action "selectBelow")
|
|
fillGapBefore=(action "fillGapBefore")
|
|
fillGapAfter=(action "fillGapAfter")
|
|
showInvite=(route-action "showInvite")}}
|
|
{{/unless}}
|
|
|
|
{{conditional-loading-spinner condition=model.postStream.loadingBelow}}
|
|
</div>
|
|
<div id="topic-bottom"></div>
|
|
|
|
{{#conditional-loading-spinner condition=model.postStream.loadingFilter}}
|
|
{{#if loadedAllPosts}}
|
|
|
|
{{#if model.pending_posts}}
|
|
<div class='pending-posts'>
|
|
{{#each model.pending_posts as |pending|}}
|
|
<div class='reviewable-item'>
|
|
<div class='reviewable-meta-data'>
|
|
<span class='reviewable-type'>
|
|
{{i18n "review.awaiting_approval"}}
|
|
</span>
|
|
<span class='created-at'>
|
|
{{age-with-tooltip pending.created_at}}
|
|
</span>
|
|
</div>
|
|
<div class='post-contents-wrapper'>
|
|
{{reviewable-created-by user=currentUser tagName=''}}
|
|
<div class='post-contents'>
|
|
{{reviewable-created-by-name user=currentUser tagName=''}}
|
|
<div class='post-body'>{{cook-text pending.raw}}</div>
|
|
</div>
|
|
</div>
|
|
<div class='reviewable-actions'>
|
|
{{d-button
|
|
class="btn-danger"
|
|
label="review.delete"
|
|
icon="trash-alt"
|
|
action=(action "deletePending" pending) }}
|
|
</div>
|
|
</div>
|
|
{{/each}}
|
|
</div>
|
|
{{/if}}
|
|
|
|
{{#if model.queued_posts_count}}
|
|
<div class="has-pending-posts">
|
|
<div>
|
|
{{{i18n "review.topic_has_pending" count=model.queued_posts_count}}}
|
|
</div>
|
|
|
|
{{#link-to 'review' (query-params topic_id=model.id type="ReviewableQueuedPost" status="pending")}}
|
|
{{i18n "review.view_pending"}}
|
|
{{/link-to}}
|
|
</div>
|
|
{{/if}}
|
|
|
|
{{#if model.private_topic_timer.execute_at}}
|
|
{{topic-timer-info
|
|
topicClosed=model.closed
|
|
statusType=model.private_topic_timer.status_type
|
|
executeAt=model.private_topic_timer.execute_at
|
|
duration=model.private_topic_timer.duration
|
|
removeTopicTimer=(action "removeTopicTimer" model.private_topic_timer.status_type "private_topic_timer")}}
|
|
{{/if}}
|
|
|
|
{{topic-timer-info
|
|
topicClosed=model.closed
|
|
statusType=model.topic_timer.status_type
|
|
executeAt=model.topic_timer.execute_at
|
|
basedOnLastPost=model.topic_timer.based_on_last_post
|
|
duration=model.topic_timer.duration
|
|
categoryId=model.topic_timer.category_id
|
|
removeTopicTimer=(action "removeTopicTimer" model.topic_timer.status_type "topic_timer")}}
|
|
|
|
{{#if session.showSignupCta}}
|
|
{{! replace "Log In to Reply" with the infobox }}
|
|
{{signup-cta}}
|
|
{{else}}
|
|
{{#if currentUser}}
|
|
{{plugin-outlet name="topic-above-footer-buttons" args=(hash model=model)}}
|
|
|
|
{{topic-footer-buttons
|
|
topic=model
|
|
toggleMultiSelect=(action "toggleMultiSelect")
|
|
deleteTopic=(action "deleteTopic")
|
|
recoverTopic=(action "recoverTopic")
|
|
toggleClosed=(action "toggleClosed")
|
|
toggleArchived=(action "toggleArchived")
|
|
toggleVisibility=(action "toggleVisibility")
|
|
showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
|
|
showFeatureTopic=(route-action "showFeatureTopic")
|
|
showChangeTimestamp=(route-action "showChangeTimestamp")
|
|
resetBumpDate=(action "resetBumpDate")
|
|
convertToPublicTopic=(action "convertToPublicTopic")
|
|
convertToPrivateMessage=(action "convertToPrivateMessage")
|
|
toggleBookmark=(action "toggleBookmark")
|
|
showFlagTopic=(route-action "showFlagTopic")
|
|
toggleArchiveMessage=(action "toggleArchiveMessage")
|
|
editFirstPost=(action "editFirstPost")
|
|
deferTopic=(action "deferTopic")
|
|
replyToPost=(action "replyToPost")
|
|
toggleFeaturedOnProfile=(action "toggleFeaturedOnProfile")}}
|
|
{{else}}
|
|
<div id="topic-footer-buttons">
|
|
{{d-button icon="reply" class="btn-primary pull-right" action=(route-action "showLogin") label="topic.reply.title"}}
|
|
</div>
|
|
{{/if}}
|
|
{{/if}}
|
|
|
|
{{#if showSelectedPostsAtBottom}}
|
|
<div class='selected-posts {{unless multiSelect 'hidden'}}'>
|
|
{{partial "selected-posts"}}
|
|
</div>
|
|
{{/if}}
|
|
|
|
{{plugin-outlet name="topic-above-suggested" args=(hash model=model)}}
|
|
<div class="{{if model.relatedMessages.length 'related-messages-wrapper'}} {{if model.suggestedTopics.length 'suggested-topics-wrapper'}}">
|
|
{{#if model.relatedMessages.length}}
|
|
{{related-messages topic=model}}
|
|
{{/if}}
|
|
{{#if model.suggestedTopics.length}}
|
|
{{suggested-topics topic=model}}
|
|
{{/if}}
|
|
</div>
|
|
{{/if}}
|
|
{{/conditional-loading-spinner}}
|
|
|
|
</section>
|
|
</div>
|
|
|
|
</div>
|
|
{{else}}
|
|
<div class="container">
|
|
{{#conditional-loading-spinner condition=noErrorYet}}
|
|
{{#if model.errorHtml}}
|
|
<div class="not-found">{{{model.errorHtml}}}</div>
|
|
{{else}}
|
|
<div class="topic-error">
|
|
<div>{{model.errorMessage}}</div>
|
|
{{#if model.noRetry}}
|
|
{{#unless currentUser}}
|
|
{{d-button action=(route-action "showLogin") class="btn-primary topic-retry" icon="user" label="log_in"}}
|
|
{{/unless}}
|
|
{{else}}
|
|
{{d-button action=(action "retryLoading") class="btn-primary topic-retry" icon="sync" label="errors.buttons.again"}}
|
|
{{/if}}
|
|
</div>
|
|
{{conditional-loading-spinner condition=retrying}}
|
|
{{/if}}
|
|
{{/conditional-loading-spinner}}
|
|
</div>
|
|
{{/if}}
|
|
|
|
{{share-popup topic=model replyAsNewTopic=(action "replyAsNewTopic")}}
|
|
|
|
{{#if embedQuoteButton}}
|
|
{{quote-button quoteState=quoteState selectText=(action "selectText")}}
|
|
{{/if}}
|
|
{{/discourse-topic}}
|