zomg text collapse is finally back #thankgod
This commit is contained in:
parent
d903ab7686
commit
1a521a792a
6 changed files with 291 additions and 141 deletions
|
|
@ -11,7 +11,7 @@
|
|||
%span.from
|
||||
= person_link(comment.author, :class => "hovercardable")
|
||||
|
||||
%span{:class => direction_for(comment.text)}
|
||||
%span{:class => [direction_for(comment.text), 'collapsible']}
|
||||
= markdownify(comment, :oembed => true, :youtube_maps => comment.youtube_titles)
|
||||
|
||||
.comment_info
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
- for photo in photos[1..photos.size]
|
||||
= link_to (image_tag photo.url(:thumb_small), :class => 'stream-photo thumb_small', 'data-small-photo' => photo.url(:thumb_medium), 'data-full-photo' => photo.url), photo_path(photo), :class => 'stream-photo-link'
|
||||
|
||||
%div{:class => direction_for(post.text)}
|
||||
%div{:class => [direction_for(post.text), 'collapsible']}
|
||||
!= markdownify(post, :youtube_maps => post[:youtube_titles])
|
||||
- if post.o_embed_cache_id.present?
|
||||
= o_embed_html(post.o_embed_cache)
|
||||
|
|
|
|||
|
|
@ -10,9 +10,6 @@ module Diaspora
|
|||
auto_link(link, :link => :urls, :html => { :target => "_blank" })
|
||||
end
|
||||
|
||||
def paragraph(text)
|
||||
"<p>#{text} </p>".html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
397
public/javascripts/vendor/jquery.expander.js
vendored
397
public/javascripts/vendor/jquery.expander.js
vendored
|
|
@ -1,140 +1,328 @@
|
|||
/*!
|
||||
* jQuery Expander Plugin v0.7
|
||||
* jQuery Expander Plugin v1.3
|
||||
*
|
||||
* Date: Wed Aug 31 20:53:59 2011 EDT
|
||||
* Date: Sat Sep 17 00:37:34 2011 EDT
|
||||
* Requires: jQuery v1.3+
|
||||
*
|
||||
* Copyright 2011, Karl Swedberg
|
||||
* Dual licensed under the MIT and GPL licenses (just like jQuery):
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
* source: https://github.com/kswedberg/jquery-expander/
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
$.expander = {
|
||||
version: '1.3',
|
||||
defaults: {
|
||||
// the number of characters at which the contents will be sliced into two parts.
|
||||
slicePoint: 100,
|
||||
|
||||
// whether to keep the last word of the summary whole (true) or let it slice in the middle of a word (false)
|
||||
preserveWords: true,
|
||||
|
||||
// widow: a threshold of sorts for whether to initially hide/collapse part of the element's contents.
|
||||
// If after slicing the contents in two there are fewer words in the second part than
|
||||
// the value set by widow, we won't bother hiding/collapsing anything.
|
||||
widow: 4,
|
||||
|
||||
// text displayed in a link instead of the hidden part of the element.
|
||||
// clicking this will expand/show the hidden/collapsed text
|
||||
expandText: 'read more',
|
||||
expandPrefix: '… ',
|
||||
|
||||
// class names for summary element and detail element
|
||||
summaryClass: 'summary',
|
||||
detailClass: 'details',
|
||||
|
||||
// class names for <span> around "read-more" link and "read-less" link
|
||||
moreClass: 'read-more',
|
||||
lessClass: 'read-less',
|
||||
|
||||
// number of milliseconds after text has been expanded at which to collapse the text again.
|
||||
// when 0, no auto-collapsing
|
||||
collapseTimer: 0,
|
||||
|
||||
// effects for expanding and collapsing
|
||||
expandEffect: 'fadeIn',
|
||||
expandSpeed: 250,
|
||||
collapseEffect: 'fadeOut',
|
||||
collapseSpeed: 200,
|
||||
|
||||
// allow the user to re-collapse the expanded text.
|
||||
userCollapse: true,
|
||||
|
||||
// text to use for the link to re-collapse the text
|
||||
userCollapseText: 'read less',
|
||||
userCollapsePrefix: ' ',
|
||||
|
||||
|
||||
// all callback functions have the this keyword mapped to the element in the jQuery set when .expander() is called
|
||||
|
||||
onSlice: null, // function() {}
|
||||
beforeExpand: null, // function() {},
|
||||
afterExpand: null, // function() {},
|
||||
onCollapse: null // function(byUser) {}
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.expander = function(options) {
|
||||
|
||||
var opts = $.extend({}, $.fn.expander.defaults, options),
|
||||
rSlash = /\//,
|
||||
var opts = $.extend({}, $.expander.defaults, options),
|
||||
rSelfClose = /^<(?:area|br|col|embed|hr|img|input|link|meta|param).*>$/i,
|
||||
rAmpWordEnd = /(&(?:[^;]+;)?|\w+)$/,
|
||||
rOpenCloseTag = /<\/?(\w+)[^>]*>/g,
|
||||
rOpenTag = /<(\w+)[^>]*>/g,
|
||||
rCloseTag = /<\/(\w+)>/g,
|
||||
rTagPlus = /^<[^>]+>.?/,
|
||||
delayedCollapse;
|
||||
|
||||
this.each(function() {
|
||||
var cleanedTag, startTags, endTags,
|
||||
var i, l, tmp, summTagLess, summOpens, summCloses, lastCloseTag, detailText,
|
||||
$thisDetails, $readMore,
|
||||
openTagsForDetails = [],
|
||||
closeTagsForsummaryText = [],
|
||||
defined = {},
|
||||
thisEl = this,
|
||||
$this = $(this),
|
||||
$summEl = $([]),
|
||||
o = $.meta ? $.extend({}, opts, $this.data()) : opts,
|
||||
hasDetails = !!$this.find('.' + o.detailClass).length,
|
||||
hasBlocks = !!$this.find('*').filter(function() {
|
||||
var display = $(this).css('display');
|
||||
return (/^block|table|list/).test(display);
|
||||
}).length,
|
||||
el = hasBlocks ? 'div' : 'span',
|
||||
detailSelector = el + '.' + o.detailClass,
|
||||
moreSelector = 'span.' + o.moreClass,
|
||||
expandSpeed = o.expandSpeed || 0,
|
||||
allText = $this.html(),
|
||||
startText = allText.slice(0, o.slicePoint).replace(/(&([^;]+;)?|\w+)$/,'');
|
||||
allHtml = $.trim( $this.html() ),
|
||||
allText = $.trim( $this.text() ),
|
||||
summaryText = allHtml.slice(0, o.slicePoint);
|
||||
|
||||
startTags = startText.match(/<\w[^>]*>/g);
|
||||
|
||||
if (startTags) {
|
||||
startText = allText.slice(0,o.slicePoint + startTags.join('').length).replace(/(&([^;]+;)?|\w+)$/,'');
|
||||
// bail out if we've already set up the expander on this element
|
||||
if ( $.data(this, 'expander') ) {
|
||||
return;
|
||||
}
|
||||
$.data(this, 'expander', true);
|
||||
|
||||
if (startText.lastIndexOf('<') > startText.lastIndexOf('>') ) {
|
||||
startText = startText.slice(0,startText.lastIndexOf('<'));
|
||||
}
|
||||
|
||||
var defined = {};
|
||||
// determine which callback functions are defined
|
||||
$.each(['onSlice','beforeExpand', 'afterExpand', 'onCollapse'], function(index, val) {
|
||||
defined[val] = $.isFunction(o[val]);
|
||||
});
|
||||
|
||||
var endText = allText.slice(startText.length);
|
||||
// create necessary expand/collapse elements if they don't already exist
|
||||
if (!$(this).find('span.details').length) {
|
||||
// end script if text length isn't long enough.
|
||||
if ( endText.replace(/\s+$/,'').split(' ').length < o.widow || allText.length < o.slicePoint ) { return; }
|
||||
// otherwise, continue...
|
||||
if (defined.onSlice) { o.onSlice.call(thisEl); }
|
||||
if (endText.indexOf('</') > -1) {
|
||||
endTags = endText.match(/<(\/)?[^>]*>/g);
|
||||
for (var i=0; i < endTags.length; i++) {
|
||||
// back up if we're in the middle of a tag or word
|
||||
summaryText = backup(summaryText);
|
||||
|
||||
if (endTags[i].indexOf('</') > -1) {
|
||||
var startTag, startTagExists = false;
|
||||
for (var j=0; j < i; j++) {
|
||||
startTag = endTags[j].slice(0, endTags[j].indexOf(' ')).replace(/\w$/,'$1>');
|
||||
if (startTag == endTags[i].replace(rSlash,'')) {
|
||||
startTagExists = true;
|
||||
}
|
||||
}
|
||||
if (!startTagExists) {
|
||||
startText = startText + endTags[i];
|
||||
var matched = false;
|
||||
for (var s=startTags.length - 1; s >= 0; s--) {
|
||||
if (startTags[s].slice(0, startTags[s].indexOf(' ')).replace(/(\w)$/,'$1>') == endTags[i].replace(rSlash,'') &&
|
||||
!matched ) {
|
||||
cleanedTag = cleanedTag ? startTags[s] + cleanedTag : startTags[s];
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// summary text sans tags length
|
||||
summTagless = summaryText.replace(rOpenCloseTag,'').length;
|
||||
|
||||
endText = cleanedTag && cleanedTag + endText || endText;
|
||||
// add more characters to the summary, one for each character in the tags
|
||||
while (summTagless < o.slicePoint) {
|
||||
newChar = allHtml.charAt(summaryText.length);
|
||||
if (newChar == '<') {
|
||||
newChar = allHtml.slice(summaryText.length).match(rTagPlus)[0];
|
||||
}
|
||||
$this.html([
|
||||
startText,
|
||||
'<span class="read-more">',
|
||||
o.expandPrefix,
|
||||
'<a href="#">',
|
||||
o.expandText,
|
||||
'</a>',
|
||||
'</span>',
|
||||
'<span class="details">',
|
||||
endText,
|
||||
'</span>'
|
||||
].join('')
|
||||
);
|
||||
summaryText += newChar;
|
||||
summTagless++;
|
||||
}
|
||||
|
||||
var $thisDetails = $(this).find('span.details'),
|
||||
$readMore = $(this).find('span.read-more');
|
||||
summaryText = backup(summaryText, o.preserveWords);
|
||||
|
||||
// separate open tags from close tags and clean up the lists
|
||||
summOpens = summaryText.match(rOpenTag) || [];
|
||||
summCloses = summaryText.match(rCloseTag) || [];
|
||||
|
||||
// filter out self-closing tags
|
||||
tmp = [];
|
||||
$.each(summOpens, function(index, val) {
|
||||
if ( !rSelfClose.test(val) ) {
|
||||
tmp.push(val);
|
||||
}
|
||||
});
|
||||
summOpens = tmp;
|
||||
|
||||
// strip close tags to just the tag name
|
||||
l = summCloses.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
summCloses[i] = summCloses[i].replace(rCloseTag, '$1');
|
||||
}
|
||||
|
||||
// tags that start in summary and end in detail need:
|
||||
// a). close tag at end of summary
|
||||
// b). open tag at beginning of detail
|
||||
$.each(summOpens, function(index, val) {
|
||||
var thisTagName = val.replace(rOpenTag, '$1');
|
||||
var closePosition = $.inArray(thisTagName, summCloses);
|
||||
if (closePosition === -1) {
|
||||
openTagsForDetails.push(val);
|
||||
closeTagsForsummaryText.push('</' + thisTagName + '>');
|
||||
|
||||
} else {
|
||||
summCloses.splice(closePosition, 1);
|
||||
}
|
||||
});
|
||||
|
||||
// reverse the order of the close tags for the summary so they line up right
|
||||
closeTagsForsummaryText.reverse();
|
||||
|
||||
// create necessary summary and detail elements if they don't already exist
|
||||
if ( !hasDetails ) {
|
||||
|
||||
// end script if detail has fewer words than widow option
|
||||
detailText = allHtml.slice(summaryText.length);
|
||||
if ( detailText.split(/\s+/).length < o.widow && !hasDetails ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, continue...
|
||||
lastCloseTag = closeTagsForsummaryText.pop() || '';
|
||||
summaryText += closeTagsForsummaryText.join('');
|
||||
detailText = openTagsForDetails.join('') + detailText;
|
||||
|
||||
} else {
|
||||
// assume that even if there are details, we still need readMore/readLess/summary elements
|
||||
// (we already bailed out earlier when readMore el was found)
|
||||
// but we need to create els differently
|
||||
|
||||
// remove the detail from the rest of the content
|
||||
detailText = $this.find(detailSelector).remove().html();
|
||||
|
||||
// The summary is what's left
|
||||
summaryText = $this.html();
|
||||
|
||||
// allHtml is the summary and detail combined (this is needed when content has block-level elements)
|
||||
allHtml = summaryText + detailText;
|
||||
|
||||
lastCloseTag = '';
|
||||
}
|
||||
o.moreLabel = $this.find(moreSelector).length ? '' : buildMoreLabel(o);
|
||||
|
||||
if (hasBlocks) {
|
||||
detailText = allHtml;
|
||||
}
|
||||
summaryText += lastCloseTag;
|
||||
|
||||
// onSlice callback
|
||||
o.summary = summaryText;
|
||||
o.details = detailText;
|
||||
o.lastCloseTag = lastCloseTag;
|
||||
|
||||
if (defined.onSlice) {
|
||||
// user can choose to return a modified options object
|
||||
// one last chance for user to change the options. sneaky, huh?
|
||||
// but could be tricky so use at your own risk.
|
||||
tmp = o.onSlice.call(thisEl, o);
|
||||
|
||||
// so, if the returned value from the onSlice function is an object with a details property, we'll use that!
|
||||
o = tmp && tmp.details ? tmp : o;
|
||||
}
|
||||
|
||||
// build the html with summary and detail and use it to replace old contents
|
||||
var html = buildHTML(o, hasBlocks);
|
||||
$this.html( html );
|
||||
|
||||
// set up details and summary for expanding/collapsing
|
||||
$thisDetails = $this.find(detailSelector);
|
||||
$readMore = $this.find(moreSelector);
|
||||
$thisDetails.hide();
|
||||
$readMore.find('a').bind('click.expander', function(event) {
|
||||
$readMore.find('a').unbind('click.expander').bind('click.expander', expand);
|
||||
|
||||
$summEl = $this.find('div.' + o.summaryClass);
|
||||
|
||||
if ( o.userCollapse && !$this.find('span.' + o.lessClass).length ) {
|
||||
$this
|
||||
.find(detailSelector)
|
||||
.append('<span class="' + o.lessClass + '">' + o.userCollapsePrefix + '<a href="#">' + o.userCollapseText + '</a></span>');
|
||||
}
|
||||
|
||||
$this
|
||||
.find('span.' + o.lessClass + ' a')
|
||||
.unbind('click.expander')
|
||||
.bind('click.expander', function(event) {
|
||||
event.preventDefault();
|
||||
clearTimeout(delayedCollapse);
|
||||
var $detailsCollapsed = $(this).closest(detailSelector);
|
||||
reCollapse(o, $detailsCollapsed);
|
||||
if (defined.onCollapse) {
|
||||
o.onCollapse.call(thisEl, true);
|
||||
}
|
||||
});
|
||||
|
||||
function expand(event) {
|
||||
event.preventDefault();
|
||||
$readMore.hide();
|
||||
$summEl.hide();
|
||||
if (defined.beforeExpand) {
|
||||
o.beforeExpand.call(thisEl);
|
||||
}
|
||||
|
||||
$thisDetails[o.expandEffect](expandSpeed, function() {
|
||||
$thisDetails.stop(false, true)[o.expandEffect](expandSpeed, function() {
|
||||
$thisDetails.css({zoom: ''});
|
||||
if (defined.afterExpand) {o.afterExpand.call(thisEl);}
|
||||
delayCollapse(o, $thisDetails, thisEl);
|
||||
});
|
||||
});
|
||||
|
||||
if ( o.userCollapse && !$this.find('span.re-collapse').length ) {
|
||||
$this
|
||||
.find('span.details')
|
||||
.append('<span class="re-collapse">' + o.userCollapsePrefix + '<a href="#">' + o.userCollapseText + '</a></span>');
|
||||
$this.find('span.re-collapse a').bind('click.expander', function(event) {
|
||||
event.preventDefault();
|
||||
clearTimeout(delayedCollapse);
|
||||
var $detailsCollapsed = $(this).parents('span.details');
|
||||
reCollapse($detailsCollapsed);
|
||||
if (defined.onCollapse) {
|
||||
o.onCollapse.call(thisEl, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function reCollapse(el) {
|
||||
el.hide()
|
||||
.prev('span.read-more').show();
|
||||
}); // this.each
|
||||
|
||||
function buildHTML(o, blocks) {
|
||||
var el = 'span',
|
||||
summary = o.summary;
|
||||
if ( blocks ) {
|
||||
el = 'div';
|
||||
// tuck the moreLabel inside the last close tag
|
||||
summary = summary.replace(/(<\/[^>]+>)\s*$/, o.moreLabel + '$1');
|
||||
|
||||
// and wrap it in a div
|
||||
summary = '<div class="' + o.summaryClass + '">' + summary + '</div>';
|
||||
} else {
|
||||
summary += o.moreLabel;
|
||||
}
|
||||
|
||||
return [
|
||||
summary,
|
||||
'<',
|
||||
el + ' class="' + o.detailClass + '"',
|
||||
'>',
|
||||
o.details,
|
||||
'</' + el + '>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
function buildMoreLabel(o) {
|
||||
var ret = '<span class="' + o.moreClass + '">' + o.expandPrefix;
|
||||
ret += '<a href="#">' + o.expandText + '</a></span>';
|
||||
return ret;
|
||||
}
|
||||
|
||||
function backup(txt, preserveWords) {
|
||||
if ( txt.lastIndexOf('<') > txt.lastIndexOf('>') ) {
|
||||
txt = txt.slice( 0, txt.lastIndexOf('<') );
|
||||
}
|
||||
if (preserveWords) {
|
||||
txt = txt.replace(rAmpWordEnd,'');
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
|
||||
function reCollapse(o, el) {
|
||||
el.stop(true, true)[o.collapseEffect](o.collapseSpeed, function() {
|
||||
var prevMore = el.prev('span.' + o.moreClass).show();
|
||||
if (!prevMore.length) {
|
||||
el.parent().children('div.' + o.summaryClass).show()
|
||||
.find('span.' + o.moreClass).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function delayCollapse(option, $collapseEl, thisEl) {
|
||||
if (option.collapseTimer) {
|
||||
delayedCollapse = setTimeout(function() {
|
||||
reCollapse($collapseEl);
|
||||
reCollapse(option, $collapseEl);
|
||||
if ( $.isFunction(option.onCollapse) ) {
|
||||
option.onCollapse.call(thisEl, false);
|
||||
}
|
||||
|
|
@ -146,40 +334,5 @@
|
|||
};
|
||||
|
||||
// plugin defaults
|
||||
$.fn.expander.defaults = {
|
||||
// slicePoint: the number of characters at which the contents will be sliced into two parts.
|
||||
// Note: any tag names in the HTML that appear inside the sliced element before
|
||||
// the slicePoint will be counted along with the text characters.
|
||||
slicePoint: 100,
|
||||
|
||||
// widow: a threshold of sorts for whether to initially hide/collapse part of the element's contents.
|
||||
// If after slicing the contents in two there are fewer words in the second part than
|
||||
// the value set by widow, we won't bother hiding/collapsing anything.
|
||||
widow: 4,
|
||||
|
||||
// text displayed in a link instead of the hidden part of the element.
|
||||
// clicking this will expand/show the hidden/collapsed text
|
||||
expandText: 'read more',
|
||||
expandPrefix: '… ',
|
||||
|
||||
// number of milliseconds after text has been expanded at which to collapse the text again
|
||||
collapseTimer: 0,
|
||||
expandEffect: 'fadeIn',
|
||||
expandSpeed: 250,
|
||||
|
||||
// allow the user to re-collapse the expanded text.
|
||||
userCollapse: true,
|
||||
|
||||
// text to use for the link to re-collapse the text
|
||||
userCollapseText: '[collapse expanded text]',
|
||||
userCollapsePrefix: ' ',
|
||||
|
||||
|
||||
// all callback functions have the this keyword mapped to the element in the jQuery set when .expander() is called
|
||||
|
||||
onSlice: null, // function() {}
|
||||
beforeExpand: null, // function() {},
|
||||
afterExpand: null, // function() {},
|
||||
onCollapse: null // function(byUser) {}
|
||||
};
|
||||
$.fn.expander.defaults = $.expander.defaults;
|
||||
})(jQuery);
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@
|
|||
deleteCommentLink: comment.find("a.comment_delete"),
|
||||
likes: self.instantiate("Likes", comment.find(".likes_container")),
|
||||
timeAgo: self.instantiate("TimeAgo", comment.find("abbr.timeago")),
|
||||
content: comment.find(".content span")
|
||||
content: comment.find(".content span .collapsible")
|
||||
});
|
||||
|
||||
self.deleteCommentLink.click(self.removeComment);
|
||||
self.deleteCommentLink.twipsy({ trigger: "hover" });
|
||||
|
||||
// self.content.expander({
|
||||
// slicePoint: 200,
|
||||
// widow: 18,
|
||||
// expandText: Diaspora.I18n.t("show_more"),
|
||||
// userCollapse: false
|
||||
// });
|
||||
self.content.expander({
|
||||
slicePoint: 200,
|
||||
widow: 18,
|
||||
expandText: Diaspora.I18n.t("show_more"),
|
||||
userCollapse: false
|
||||
});
|
||||
|
||||
self.globalSubscribe("likes/" + self.comment.attr('id') + "/updated", function(){
|
||||
self.likes = self.instantiate("Likes", self.comment.find(".likes_container"));
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
lightBox: self.instantiate("Lightbox", element),
|
||||
timeAgo: self.instantiate("TimeAgo", element.find(".timeago a abbr.timeago")),
|
||||
|
||||
content: element.find(".content p"),
|
||||
content: element.find(".content .collapsible"),
|
||||
deletePostLink: element.find("a.stream_element_delete"),
|
||||
focusCommentLink: element.find("a.focus_comment_textarea"),
|
||||
hidePostLoader: element.find("img.hide_loader"),
|
||||
|
|
@ -26,12 +26,12 @@
|
|||
self.postScope.twipsy();
|
||||
|
||||
// collapse long posts
|
||||
// self.content.expander({
|
||||
// slicePoint: 400,
|
||||
// widow: 12,
|
||||
// expandText: Diaspora.I18n.t("show_more"),
|
||||
// userCollapse: false
|
||||
// });
|
||||
self.content.expander({
|
||||
slicePoint: 400,
|
||||
widow: 12,
|
||||
expandText: Diaspora.I18n.t("show_more"),
|
||||
userCollapse: false
|
||||
});
|
||||
|
||||
self.globalSubscribe("likes/" + self.postGuid + "/updated", function() {
|
||||
self.likes = self.instantiate("Likes", self.post.find(".likes_container:first"));
|
||||
|
|
|
|||
Loading…
Reference in a new issue