Add deletion control for attached videos
| @@ -7066,6 +7066,16 @@ | ||
| 7066 | 7066 | <div class="right_menu_button mes_file_delete fa-solid fa-trash-can" title="Remove the file" data-i18n="[title]Remove the file"></div> |
| 7067 | 7067 | </div> |
| 7068 | 7068 | </div> |
| 7069 | + | |
| 7070 | + <div id="message_video_template" class="template_element"> | |
| 7071 | + <div class="mes_video_container"> | |
| 7072 | + <div class="mes_video_controls"> | |
| 7073 | + <div><!-- Placeholder --></div> | |
| 7074 | + <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_video_delete" data-i18n="[title]Delete"></div> | |
| 7075 | + </div> | |
| 7076 | + <video class="mes_video" controls preload="metadata"></video> | |
| 7077 | + </div> | |
| 7078 | + </div> | |
| 7069 | 7079 | </div> |
| 7070 | 7080 | <div id="movingDivs"> |
| 7071 | 7081 | <div id="floatingPrompt" class="drawer-content flexGap5"> |
| @@ -2505,16 +2505,11 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { | ||
| 2505 | 2505 | |
| 2506 | 2506 | // Add video to message |
| 2507 | 2507 | if (mes.extra?.video) { |
| 2508 | 2508 | const container = messageElement.find$('#message_video_template .mes_blockmes_video_container').clone(); |
| 2509 | + messageElement.find('.mes_video_container').remove(); | |
| 2510 | + messageElement.find('.mes_block').append(container); | |
| 2509 | 2511 | const chatHeight = $('#chat').prop('scrollHeight'); |
| 2510 | - | |
| 2512 | + const video = container.find('.mes_video'); | |
| 2511 | - // Create video element if it doesn't exist | |
| 2512 | - let video = messageElement.find('.mes_video'); | |
| 2513 | - if (video.length === 0) { | |
| 2514 | - video = $('<video class="mes_video" controls preload="metadata"></video>'); | |
| 2515 | - container.append(video); | |
| 2516 | - } | |
| 2517 | - | |
| 2518 | 2513 | video.off('loadedmetadata').on('loadedmetadata', function () { |
| 2519 | 2514 | if (!adjustScroll) { |
| 2520 | 2515 | return; |
| @@ -2527,7 +2522,7 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { | ||
| 2527 | 2522 | |
| 2528 | 2523 | video.attr('src', mes.extra?.video); |
| 2529 | 2524 | } else { |
| 2530 | 2525 | messageElement.find('.mes_videomes_video_container').remove(); |
| 2531 | 2526 | } |
| 2532 | 2527 | |
| 2533 | 2528 | // Add file to message |
| @@ -893,6 +893,27 @@ async function deleteMessageImage() { | ||
| 893 | 893 | await saveChatConditional(); |
| 894 | 894 | } |
| 895 | 895 | |
| 896 | +async function deleteMessageVideo() { | |
| 897 | + const confirm = await Popup.show.confirm(t`Delete video from message?`, t`This action can't be undone.`); | |
| 898 | + if (!confirm) { | |
| 899 | + return; | |
| 900 | + } | |
| 901 | + | |
| 902 | + const mesBlock = $(this).closest('.mes'); | |
| 903 | + const mesId = mesBlock.attr('mesid'); | |
| 904 | + const message = chat[mesId]; | |
| 905 | + | |
| 906 | + if (!message?.extra?.video) { | |
| 907 | + console.warn('Message has no video or it is empty'); | |
| 908 | + return; | |
| 909 | + } | |
| 910 | + | |
| 911 | + delete message.extra.video; | |
| 912 | + mesBlock.find('.mes_video_container').remove(); | |
| 913 | + | |
| 914 | + await saveChatConditional(); | |
| 915 | +} | |
| 916 | + | |
| 896 | 917 | /** |
| 897 | 918 | * Deletes file from the server. |
| 898 | 919 | * @param {string} url Path to the file on the server |
| @@ -1856,6 +1877,7 @@ export function initChatUtilities() { | ||
| 1856 | 1877 | $(document).on('click', '.mes_img', expandMessageImage); |
| 1857 | 1878 | $(document).on('click', '.mes_img_enlarge', expandAndZoomMessageImage); |
| 1858 | 1879 | $(document).on('click', '.mes_img_delete', deleteMessageImage); |
| 1880 | + $(document).on('click', '.mes_video_delete', deleteMessageVideo); | |
| 1859 | 1881 | |
| 1860 | 1882 | $('#file_form_input').on('change', async () => { |
| 1861 | 1883 | const fileInput = document.getElementById('file_form_input'); |
| @@ -5033,8 +5033,9 @@ a:hover { | ||
| 5033 | 5033 | display: none; |
| 5034 | 5034 | } |
| 5035 | 5035 | |
| 5036 | 5036 | /* Message images/video */ |
| 5037 | 5037 | .mes .mes_img_container {, |
| 5038 | +.mes .mes_video_container { | |
| 5038 | 5039 | max-width: 100%; |
| 5039 | 5040 | /*to fit inside single window height of mobile landscape*/ |
| 5040 | 5041 | display: none; |
| @@ -5044,8 +5045,12 @@ a:hover { | ||
| 5044 | 5045 | padding: 0.5rem; |
| 5045 | 5046 | } |
| 5046 | 5047 | |
| 5048 | +.mes .mes_video_container:has(.mes_video[src]) { | |
| 5049 | + display: flex; | |
| 5050 | +} | |
| 5051 | + | |
| 5047 | 5052 | .mes_img { |
| 5048 | 5053 | border-radius: 10px5px; |
| 5049 | 5054 | max-width: 100%; |
| 5050 | 5055 | max-height: 40vh; |
| 5051 | 5056 | image-rendering: -webkit-optimize-contrast; |
| @@ -5059,7 +5064,8 @@ a:hover { | ||
| 5059 | 5064 | } |
| 5060 | 5065 | |
| 5061 | 5066 | .mes_img_swipes, |
| 5062 | 5067 | .mes_img_controls {, |
| 5068 | +.mes_video_controls { | |
| 5063 | 5069 | position: absolute; |
| 5064 | 5070 | top: 0.1em; |
| 5065 | 5071 | left: 0; |
| @@ -5070,6 +5076,8 @@ a:hover { | ||
| 5070 | 5076 | justify-content: space-between; |
| 5071 | 5077 | align-items: center; |
| 5072 | 5078 | padding: 1em; |
| 5079 | + z-index: 1; | |
| 5080 | + transition: opacity var(--animation-duration) ease-in-out; | |
| 5073 | 5081 | } |
| 5074 | 5082 | |
| 5075 | 5083 | .mes_img_swipes { |
| @@ -5078,7 +5086,8 @@ a:hover { | ||
| 5078 | 5086 | } |
| 5079 | 5087 | |
| 5080 | 5088 | .mes_img_swipes .right_menu_button, |
| 5081 | 5089 | .mes_img_controls .right_menu_button {, |
| 5090 | +.mes_video_controls .right_menu_button { | |
| 5082 | 5091 | filter: brightness(90%); |
| 5083 | 5092 | text-shadow: 1px 1px var(--SmartThemeShadowColor) !important; |
| 5084 | 5093 | padding: 1px; |
| @@ -5087,23 +5096,29 @@ a:hover { | ||
| 5087 | 5096 | } |
| 5088 | 5097 | |
| 5089 | 5098 | .mes_img_swipes .right_menu_button::before, |
| 5090 | 5099 | .mes_img_controls .right_menu_button::before {, |
| 5100 | +.mes_video_controls .right_menu_button::before { | |
| 5091 | 5101 | /* Fix weird alignment with this font-awesome icons on focus */ |
| 5092 | 5102 | position: relative; |
| 5093 | 5103 | top: 0.6125em; |
| 5094 | 5104 | } |
| 5095 | 5105 | |
| 5096 | 5106 | .mes_img_swipes .right_menu_button:hover, |
| 5097 | 5107 | .mes_img_controls .right_menu_button:hover {, |
| 5108 | +.mes_video_controls .right_menu_button:hover { | |
| 5098 | 5109 | filter: brightness(150%); |
| 5099 | 5110 | } |
| 5100 | 5111 | |
| 5112 | +/* It's better to show controls only when the video is not playing */ | |
| 5113 | +/* but :playing pseudo-class is not supported in all browsers */ | |
| 5114 | +/* .mes_video_container:focus-within .mes_video_controls:not(:has(.mes_video:playing)), */ | |
| 5101 | 5115 | .mes_img_container:has(.mes_img.error) .mes_img_swipes, |
| 5102 | 5116 | .mes_img_container:has(.mes_img.error) .mes_img_controls, |
| 5103 | 5117 | .mes_img_container:hover .mes_img_swipes, |
| 5104 | 5118 | .mes_img_container:focus-within .mes_img_swipes, |
| 5105 | 5119 | .mes_img_container:hover .mes_img_controls, |
| 5106 | 5120 | .mes_img_container:focus-within .mes_img_controls {, |
| 5121 | +.mes_video_container:hover .mes_video_controls { | |
| 5107 | 5122 | opacity: 1; |
| 5108 | 5123 | } |
| 5109 | 5124 | |
| @@ -5203,9 +5218,8 @@ body:not(.sd) .mes_img_swipes { | ||
| 5203 | 5218 | .mes_video { |
| 5204 | 5219 | max-width: 100%; |
| 5205 | 5220 | max-height: 400px; |
| 5206 | 5221 | border-radius: 8px5px; |
| 5207 | 5222 | background: #000; |
| 5208 | - margin: 0.5rem; | |
| 5209 | 5223 | } |
| 5210 | 5224 | |
| 5211 | 5225 | /* Ensure video controls are visible */ |