Port 'more' button in previews to Vue
This commit is contained in:
parent
1cd28a5ccf
commit
d83dcc35e2
@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="link.shown && link.canDisplay"
|
||||
ref="container"
|
||||
class="preview">
|
||||
<div :class="['toggle-content', 'toggle-type-' + link.type]">
|
||||
<div
|
||||
ref="content"
|
||||
:class="['toggle-content', 'toggle-type-' + link.type, { opened: isContentShown }]">
|
||||
<template v-if="link.type === 'link'">
|
||||
<a
|
||||
v-if="link.thumb"
|
||||
@ -28,14 +31,12 @@
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="showMoreButton"
|
||||
:aria-expanded="isContentShown"
|
||||
:aria-label="moreButtonLabel"
|
||||
class="more"
|
||||
aria-expanded="false"
|
||||
aria-label="More"
|
||||
data-closed-text="More"
|
||||
data-opened-text="Less"
|
||||
>
|
||||
<span class="more-caret"/>
|
||||
</button>
|
||||
@click="onMoreClick"
|
||||
><span class="more-caret"/></button>
|
||||
</div>
|
||||
|
||||
<div class="body overflowable">
|
||||
@ -105,11 +106,10 @@
|
||||
</div>
|
||||
|
||||
<button
|
||||
:aria-expanded="isContentShown"
|
||||
:aria-label="moreButtonLabel"
|
||||
class="more"
|
||||
aria-expanded="false"
|
||||
aria-label="More"
|
||||
data-closed-text="More"
|
||||
data-opened-text="Less"
|
||||
@click="onMoreClick"
|
||||
><span class="more-caret"/></button>
|
||||
</template>
|
||||
</template>
|
||||
@ -123,14 +123,40 @@ export default {
|
||||
props: {
|
||||
link: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMoreButton: false,
|
||||
isContentShown: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
moreButtonLabel() {
|
||||
return this.isContentShown ? "Less" : "More";
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const options = require("../js/options");
|
||||
this.$set(this.link, "canDisplay", this.link.type !== "loading" && options.shouldOpenMessagePreview(this.link.type));
|
||||
|
||||
if (this.link.type !== "link") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (!this.$refs.content) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showMoreButton = this.$refs.content.offsetWidth >= this.$refs.container.offsetWidth;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onPreviewReady() {
|
||||
|
||||
},
|
||||
onMoreClick() {
|
||||
this.isContentShown = !this.isContentShown;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<button
|
||||
v-if="link.canDisplay"
|
||||
@click="onClick"
|
||||
:class="['toggle-button', 'toggle-preview', { opened: link.shown }]"/>
|
||||
:class="['toggle-button', 'toggle-preview', { opened: link.shown }]"
|
||||
@click="onClick"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -12,11 +12,11 @@ export default {
|
||||
link: Object,
|
||||
},
|
||||
methods: {
|
||||
onClick: function() {
|
||||
onClick() {
|
||||
this.link.shown = !this.link.shown;
|
||||
|
||||
this.$parent.$emit("linkPreviewToggle", this.link, this.$parent.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -35,8 +35,8 @@
|
||||
<Message
|
||||
v-else
|
||||
:message="message"
|
||||
@linkPreviewToggle="onLinkPreviewToggle"
|
||||
:key="message.id"/>
|
||||
:key="message.id"
|
||||
@linkPreviewToggle="onLinkPreviewToggle"/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -2,8 +2,8 @@
|
||||
const parse = require("../js/libs/handlebars/parse");
|
||||
|
||||
export default {
|
||||
functional: true,
|
||||
name: "ParsedMessage",
|
||||
functional: true,
|
||||
props: {
|
||||
message: Object,
|
||||
},
|
||||
|
@ -101,7 +101,7 @@ function createVueFragment(fragment, createElement) {
|
||||
return createElement("span", {
|
||||
class: classes,
|
||||
style: {
|
||||
color: `#${fragment.hexColor}`,
|
||||
"color": `#${fragment.hexColor}`,
|
||||
"background-color": fragment.hexBgColor ? `#${fragment.hexBgColor}` : null,
|
||||
},
|
||||
}, fragment.text);
|
||||
@ -157,16 +157,14 @@ module.exports = function parse(text, message = null, createElement = null) {
|
||||
link: preview,
|
||||
},
|
||||
}, fragments)];
|
||||
|
||||
//`<button class="toggle-button toggle-preview" data-url="${escapedLink}" hidden></button>`;
|
||||
} else if (textPart.channel) {
|
||||
return createElement("span", {
|
||||
class: [
|
||||
"inline-channel",
|
||||
],
|
||||
attrs: {
|
||||
role: "button",
|
||||
tabindex: 0,
|
||||
"role": "button",
|
||||
"tabindex": 0,
|
||||
"data-chan": textPart.channel,
|
||||
},
|
||||
}, fragments);
|
||||
@ -176,7 +174,7 @@ module.exports = function parse(text, message = null, createElement = null) {
|
||||
"emoji",
|
||||
],
|
||||
attrs: {
|
||||
role: "img",
|
||||
"role": "img",
|
||||
"aria-label": emojiMap[textPart.emoji] ? `Emoji: ${emojiMap[textPart.emoji]}` : null,
|
||||
},
|
||||
}, fragments);
|
||||
@ -187,7 +185,7 @@ module.exports = function parse(text, message = null, createElement = null) {
|
||||
colorClass(textPart.nick),
|
||||
],
|
||||
attrs: {
|
||||
role: "button",
|
||||
"role": "button",
|
||||
"data-name": textPart.nick,
|
||||
},
|
||||
}, fragments);
|
||||
@ -196,7 +194,7 @@ module.exports = function parse(text, message = null, createElement = null) {
|
||||
return fragments;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Merge the styling information with the channels / URLs / nicks / text objects and
|
||||
// generate HTML strings with the resulting fragments
|
||||
return merge(parts, styleFragments, cleanText).map((textPart) => {
|
||||
|
@ -176,7 +176,6 @@ window.vueMounted = () => {
|
||||
.addClass("active")
|
||||
.trigger("show");
|
||||
|
||||
utils.togglePreviewMoreButtonsIfNeeded();
|
||||
utils.updateTitle();
|
||||
|
||||
const type = chan.data("type");
|
||||
|
@ -3,130 +3,14 @@
|
||||
const $ = require("jquery");
|
||||
const debounce = require("lodash/debounce");
|
||||
const Mousetrap = require("mousetrap");
|
||||
|
||||
const options = require("./options");
|
||||
const socket = require("./socket");
|
||||
const templates = require("../views");
|
||||
const chat = $("#chat");
|
||||
|
||||
const {togglePreviewMoreButtonsIfNeeded} = require("./utils");
|
||||
|
||||
module.exports = renderPreview;
|
||||
|
||||
function renderPreview(preview, msg) {
|
||||
if (preview.type === "loading") {
|
||||
return;
|
||||
}
|
||||
|
||||
preview.shown = preview.shown && options.shouldOpenMessagePreview(preview.type);
|
||||
|
||||
const template = $(templates.msg_preview({preview}));
|
||||
const image = template.find("img, video, audio").first();
|
||||
|
||||
if (image.length === 0) {
|
||||
return appendPreview(preview, msg, template);
|
||||
}
|
||||
|
||||
const loadEvent = image.prop("tagName") === "IMG" ? "load" : "canplay";
|
||||
|
||||
// If there is an image in preview, wait for it to load before appending it to DOM
|
||||
// This is done to prevent problems keeping scroll to the bottom while images load
|
||||
image.on(`${loadEvent}.preview`, () => {
|
||||
image.off(".preview");
|
||||
|
||||
appendPreview(preview, msg, template);
|
||||
});
|
||||
|
||||
// If the image fails to load, remove it from DOM and still render the preview
|
||||
if (preview.type === "link") {
|
||||
image.on("abort.preview error.preview", () => {
|
||||
image.parent().remove();
|
||||
|
||||
appendPreview(preview, msg, template);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function appendPreview(preview, msg, template) {
|
||||
const escapedLink = preview.link.replace(/["\\]/g, "\\$&");
|
||||
const previewContainer = msg.find(`.preview[data-url="${escapedLink}"]`);
|
||||
|
||||
// This is to fix a very rare case of rendering a preview twice
|
||||
// This happens when a very large amount of messages is being sent to the client
|
||||
// and they get queued, so the `preview` object on the server has time to load before
|
||||
// it actually gets sent to the server, which makes the loaded preview sent twice,
|
||||
// once in `msg` and another in `msg:preview`
|
||||
if (!previewContainer.is(":empty")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = msg.closest(".chat");
|
||||
const channel = container.closest(".chan");
|
||||
const channelId = channel.data("id") || -1;
|
||||
const activeChannelId = chat.find(".chan.active").data("id") || -2;
|
||||
|
||||
msg.find(`.text a[href="${escapedLink}"]`)
|
||||
.first()
|
||||
.after(templates.msg_preview_toggle({preview}).trim());
|
||||
|
||||
previewContainer.append(template);
|
||||
|
||||
const moreBtn = previewContainer.find(".more");
|
||||
const previewContent = previewContainer.find(".toggle-content");
|
||||
|
||||
// Depending on the size of the preview and the text within it, show or hide a
|
||||
// "More" button that allows users to expand without having to open the link.
|
||||
// Warning: Make sure to call this only on active channel, link previews only,
|
||||
// expanded only.
|
||||
const showMoreIfNeeded = () => {
|
||||
const isVisible = moreBtn.is(":visible");
|
||||
const shouldShow = previewContent[0].offsetWidth >= previewContainer[0].offsetWidth;
|
||||
|
||||
if (!isVisible && shouldShow) {
|
||||
moreBtn.show();
|
||||
} else if (isVisible && !shouldShow) {
|
||||
togglePreviewMore(moreBtn, false);
|
||||
moreBtn.hide();
|
||||
}
|
||||
};
|
||||
|
||||
// "More" button only applies on text previews
|
||||
if (preview.type === "link") {
|
||||
// This event is triggered when a side menu is opened/closed, or when the
|
||||
// preview gets expanded/collapsed.
|
||||
previewContent.on("showMoreIfNeeded",
|
||||
() => window.requestAnimationFrame(showMoreIfNeeded)
|
||||
);
|
||||
}
|
||||
|
||||
if (activeChannelId === channelId) {
|
||||
// If this preview is in active channel, hide "More" button if necessary
|
||||
previewContent.trigger("showMoreIfNeeded");
|
||||
}
|
||||
}
|
||||
|
||||
// On resize, previews in the current channel that are expanded need to compute
|
||||
// their "More" button. Debounced handler to avoid performance cost.
|
||||
$(window).on("resize", debounce(togglePreviewMoreButtonsIfNeeded, 150));
|
||||
|
||||
$("#chat").on("click", ".toggle-content .more", function() {
|
||||
togglePreviewMore($(this));
|
||||
return false;
|
||||
});
|
||||
|
||||
function togglePreviewMore(moreBtn, state = undefined) {
|
||||
moreBtn.closest(".toggle-content").toggleClass("opened", state);
|
||||
const isExpanded = moreBtn.closest(".toggle-content").hasClass("opened");
|
||||
|
||||
moreBtn.attr("aria-expanded", isExpanded);
|
||||
|
||||
if (isExpanded) {
|
||||
moreBtn.attr("aria-label", moreBtn.data("opened-text"));
|
||||
} else {
|
||||
moreBtn.attr("aria-label", moreBtn.data("closed-text"));
|
||||
}
|
||||
}
|
||||
|
||||
/* Image viewer */
|
||||
|
||||
const imageViewer = $("#image-viewer");
|
||||
|
Loading…
Reference in New Issue
Block a user