Compile several (sass|css) files into multiple css output with Webpack 4 - sass

I have a WP Theme project.
I´m using Webpack 4 to compile sass files and merge them with css.
project structure
dev
|_ my-theme
|_css
| |_common
| | |_main.sass
| | |_normalize.css
| |_home
| |_home.sass
|_style.sass
I would like to do compile like this:
dist/my-theme/style.css = style.sass include normalize.css and main.sass in the right order
dist/my-theme/css/home/home.css = home.sass
For the moment I managed to do a unique output with the first point.
I saw there is a solution with extract-text-webpack-plugin but it´s deprecated with Webpack 4 so I´m using mini-css-extract-plugin but I have got no idea how doing this.
my current webpack.config.js
const PLUGIN_DIR_NAME = "mytheme";
const GENERAL_CONFIG_INI = "../config/config.ini";
const THEME_CONFIG_INI = "./config/config.ini";
const path = require('path');
const Promise = require('bluebird');
const fs = Promise.promisifyAll( require('fs') );
const ini = require('ini');
// include the css extraction and minification plugins
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyPlugin = require('copy-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = env => {
const global_config = ini.parse( fs.readFileSync( GENERAL_CONFIG_INI, 'utf-8' ) );
const theme_config = ini.parse( fs.readFileSync( THEME_CONFIG_INI, 'utf-8' ) );
var a_author_uri = global_config.author_uri.split(":");
var author_protocol = a_author_uri[0];
var author_uri_without_protocol = a_author_uri[1].replace( "//", "" );
var a_author_uri_domain = author_uri_without_protocol.split(".");
var author_uri_domain_name, author_uri_extension;
if( a_author_uri_domain.length === 3 ){
author_uri_domain_name = a_author_uri_domain[1];
author_uri_extension = a_author_uri_domain[2];
}else if( a_author_uri_domain.length === 2 ){
author_uri_domain_name = a_author_uri_domain[0];
author_uri_extension = a_author_uri_domain[1];
}
var a_prev_theme_version = theme_config.theme_infos.version.split(".");
var prev_major_theme_version = parseInt( a_prev_theme_version[0] );
var prev_minor_theme_version = parseInt( a_prev_theme_version[1] );
var prev_patch_theme_version = parseInt( a_prev_theme_version[2] );
var prev_build_theme_version = parseInt( a_prev_theme_version[3] );
var next_build_theme_version, next_patch_theme_version, next_minor_theme_version, next_major_theme_version;
if( env.version === "build" ){
next_build_theme_version = prev_build_theme_version + 1;
next_patch_theme_version = prev_patch_theme_version;
next_minor_theme_version = prev_minor_theme_version;
next_major_theme_version = prev_major_theme_version;
}else if( env.version === "patch" ){
next_build_theme_version = 0;
next_patch_theme_version = prev_patch_theme_version + 1;
next_minor_theme_version = prev_minor_theme_version;
next_major_theme_version = prev_major_theme_version;
}else if( env.version === "minor" ){
next_build_theme_version = 0;
next_patch_theme_version = 0;
next_minor_theme_version = prev_minor_theme_version + 1;
next_major_theme_version = prev_major_theme_version;
}else if( env.version === "major" ){
next_build_theme_version = 0;
next_patch_theme_version = 0;
next_minor_theme_version = 0;
next_major_theme_version = prev_major_theme_version + 1;
}
theme_config.theme_infos.version = next_major_theme_version + "." + next_minor_theme_version + "." + next_patch_theme_version + "." + next_build_theme_version;
fs.writeFileSync( THEME_CONFIG_INI, ini.stringify( theme_config, {} ) );
return{
plugins: [
new CleanWebpackPlugin(),
// extract css into dedicated file
new MiniCssExtractPlugin({
filename: './' + PLUGIN_DIR_NAME + '/style.css'
}),
//Only copy php files
new CopyPlugin([
{
from: 'dev',
ignore: ['*.sass', 'next/css/common/**/*.css'],
},
])
],
entry: [ './dev/' + PLUGIN_DIR_NAME + '/style.sass', './dev/' + PLUGIN_DIR_NAME + '/css/common/normalize.css', './dev/' + PLUGIN_DIR_NAME + '/css/common/main.sass' ],
output: {
path: path.resolve(__dirname, 'dist/'),
},
module: {
rules: [
// compile all .scss files to plain old css
{
test: [/css\/common\/.*\.(css|sass)$/],
use: [{loader: MiniCssExtractPlugin.loader},
{loader: 'css-loader'},
{
loader: 'sass-loader',
options: {
prependData: '$theme-name: ' + theme_config.theme_infos.name +
';$author:' + global_config.author +
';$author-uri-protocol:' + author_protocol +
';$author-uri-domain:' + author_uri_domain_name +
';$author-uri-extension:' + author_uri_extension +
';$description:' + theme_config.theme_infos.description.replace(/:/g, "%3A").replace(/;/g, "%3B") + ';' +
';$version-major:' + next_major_theme_version + ';' +
';$version-minor:' + next_minor_theme_version + ';' +
';$version-patch:' + next_patch_theme_version + ';' +
';$version-build:' + next_build_theme_version + ';',
}
}
]
}
]
}
}
};

I found a solution using MiniCssExtractPlugin.
The solution was here.
Basicaly you can change the json array as entry value with an object.
before
entry: [ './dev/' + PLUGIN_DIR_NAME + '/style.sass', './dev/' + PLUGIN_DIR_NAME + '/css/common/normalize.css', './dev/' + PLUGIN_DIR_NAME + '/css/common/main.sass' ],
after
entry:{
style: [ './dev/' + PLUGIN_DIR_NAME + '/style.sass', './dev/' + PLUGIN_DIR_NAME + '/css/common/normalize.css', './dev/' + PLUGIN_DIR_NAME + '/css/common/main.sass' ],
home: [ './dev/' + PLUGIN_DIR_NAME + '/css/home/home.sass' ]
},
If you want that one of css files is inside a subdirectory like css/home.css :
entry:{
style: [ './dev/' + PLUGIN_DIR_NAME + '/style.sass', './dev/' + PLUGIN_DIR_NAME + '/css/common/normalize.css', './dev/' + PLUGIN_DIR_NAME + '/css/common/main.sass' ],
'css/home': [ './dev/' + PLUGIN_DIR_NAME + '/css/home/home.sass' ]
},

Related

jQuery Bezier.js Plugin not redrawing on window resize

Working with this plugin. Demo here https://www.jqueryscript.net/demo/animated-lines-elements-bezier/
On this demo when you resize the page it redraws. When I use this locally it doesn't redraw and the reason seems to be the this.selector is undefined when I run locally so after it clears the svg's it can't recreate them. Anyone have any idea why this might be?
See // Bug: comment below. Is this.selector a special variable for jquery plugins because I don't see it defined anywhere here, but the demo works so not sure how.
(function ($) {
$.fn.extend({
bezier: function (options) {
var defaults = {
strokeColor: '#999',
strokeWidth: 2,
opacity: 1,
fill: 'none',
animate: true,
animationDirection: 'right',
animationDuration: 0.75
};
// Color wheel stuff
var color_wheel = [];
var frequency = .3;
for (var i = 0; i < 32; ++i) {
red = Math.sin(frequency * i + 0) * 127 + 128;
green = Math.sin(frequency * i + (2 * Math.PI / 3)) * 127 + 128;
blue = Math.sin(frequency * i + (4 * Math.PI / 3)) * 127 + 128;
color_wheel.push(RGB2Color(red, green, blue));
}
var color_index = 0;
function RGB2Color(r, g, b) {
return '#' + byte2Hex(r) + byte2Hex(g) + byte2Hex(b);
}
function byte2Hex(n) {
var nybHexString = "0123456789ABCDEF";
return String(nybHexString.substr((n >> 4) & 0x0F, 1)) + nybHexString.substr(n & 0x0F, 1);
}
// Color wheel stuff
//override default options with user set options
var settings = $.extend(defaults, options);
var me = {};
me.init = function (initObj) {
if (initObj) {
$.each(initObj, function (index, value) {
settings[index] = value;
});
}
};
me.set = function (prop, val) {
settings[prop] = val;
};
me.on = function (el1, el2) {
var $el1 = $(el1);
var $el2 = $(el2);
if ($el1.length && $el2.length) {
var svgheight, p, svgleft, svgtop, svgwidth;
var el1pos = $(el1).offset();
var el2pos = $(el2).offset();
var el1H = $(el1).outerHeight();
var el1W = $(el1).outerWidth();
var el2H = $(el2).outerHeight();
var el2W = $(el2).outerWidth();
svgleft = Math.round(el1pos.left + el1W);
svgwidth = Math.round(el2pos.left - svgleft);
var curvinessFactor, cpt;
////Determine which is higher/lower
if ((el2pos.top + (el2H / 2)) <= (el1pos.top + (el1H / 2))) {
// console.log("low to high");
svgheight = Math.round((el1pos.top + el1H / 2) - (el2pos.top + el2H / 2));
svgtop = Math.round(el2pos.top + el2H / 2) - settings.strokeWidth;
cpt = Math.round(svgwidth * Math.min(svgheight / 300, 1));
p = "M0," + (svgheight + settings.strokeWidth) + " C" + cpt + "," + (svgheight + settings.strokeWidth) + " " + (svgwidth - cpt) + "," + settings.strokeWidth + " " + svgwidth + "," + settings.strokeWidth;
}
else {
// console.log("high to low");
svgheight = Math.round((el2pos.top + el2H / 2) - (el1pos.top + el1H / 2));
svgtop = Math.round(el1pos.top + el1H / 2) - settings.strokeWidth;
cpt = Math.round(svgwidth * Math.min(svgheight / 300, 1));
p = "M0," + settings.strokeWidth + " C" + cpt + ",0 " + (svgwidth - cpt) + "," + (svgheight + settings.strokeWidth) + " " + svgwidth + "," + (svgheight + settings.strokeWidth);
}
$ropebag = $('#ropebag').length ? $('#ropebag') : $('main').append($("<div id='ropebag' />")).find('#ropebag');
var svgnode = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
var newpath = document.createElementNS('http://www.w3.org/2000/svg', "path");
var color_to_use = (settings.strokeColor != 'rainbow') ? settings.strokeColor : color_wheel[color_index];
color_index = color_index + 1;
if (color_index == 32) {
color_index = 0;
}
newpath.setAttributeNS(null, "d", p);
newpath.setAttributeNS(null, "stroke", color_to_use);
newpath.setAttributeNS(null, "stroke-width", settings.strokeWidth);
newpath.setAttributeNS(null, "opacity", settings.opacity);
newpath.setAttributeNS(null, "fill", settings.fill);
svgnode.appendChild(newpath);
$(svgnode).css({
left: svgleft,
top: svgtop,
position: 'absolute',
width: svgwidth,
height: svgheight + settings.strokeWidth * 2,
minHeight: '20px'
});
$ropebag.append(svgnode);
if (settings.animate) {
var pl = newpath.getTotalLength();
// Set up the starting positions
newpath.style.strokeDasharray = pl + ' ' + pl;
if (settings.animationDirection == 'right') {
newpath.style.strokeDashoffset = pl;
}
else {
newpath.style.strokeDashoffset = -pl;
}
// IE sucks, dont expect this to work.
newpath.getBoundingClientRect();
newpath.style.transition = newpath.style.WebkitTransition = 'stroke-dashoffset ' + settings.animationDuration + 's ease-in-out';
newpath.style.strokeDashoffset = '0';
}
}
};
me.off = function () {
$("#ropebag").empty();
};
// Redraw upon resizing the window
var selector = this.selector;
$(window).resize(function () {
me.off();
// BUG: selector is undefined
console.log(selector);
$(selector).each(function () {
console.log("looping over eac comp");
var theID = this.id;
$("." + theID).each(function (i, e) {
var rand = Math.random() * 0.7 + 0.3;
me.set('animationDuration', rand);
me.on($("#" + theID), e);
console.log("Window resize" + theID);
});
});
});
// Loop through each parent and draw the lines connecting children
return this.each(function () {
var theID = this.id;
$("." + theID).each(function (i, e) {
var rand = Math.random() * 0.7 + 0.3;
me.set('animationDuration', rand);
me.on($("#" + theID), e);
});
});
}
});
})(jQuery);

NVD3: Add the 'total' value to stackedAreaChart's interactiveGuideline

Is there a simple way to include the TOTAL value in the stackedAreaChart's interactiveGuideline tooltip? It'd be great if I could display total users using this awesome tooltip as well.
If not, what's the cleanest alternative?
You can use interactiveGuideline.tooltip.contentGenerator
https://github.com/novus/nvd3/blob/master/src/tooltip.js#L316
e.g.
chart.multiChart.interactiveLayer.tooltip.contentGenerator(function(d) {
var header = d.value;
var headerhtml =
"<thead><tr><td colspan='3'><strong class='x-value'>" +
header +
"</strong></td></tr></thead>";
var bodyhtml = "<tbody>";
var series = d.series;
series.forEach(function(c) {
var value = (c.value || 0).toFixed(2);
if (
c.key === keyForActualGreaterThanPredicted ||
c.key === keyForActualLessThanPredicted
) {
var diff = Math.abs(c.data.y0 - c.data.y1);
if (diff === 0) {
value = "-";
} else {
value = diff.toFixed(2);
}
}
bodyhtml =
bodyhtml +
"<tr><td class='legend-color-guide'><div style='background-color: " +
c.color +
";'></div></td><td class='key'>" +
c.key +
"</td><td class='value'>" +
value +
"</td></tr>";
});
bodyhtml = bodyhtml + "</tbody>";
return "<table>" + headerhtml + "" + bodyhtml + "</table>";
});

How do I return the XPath of a tag using XUL?

I would like to select the XPath of a tag and the content of the tag, something similar to what Evernote is doing. However, I cannot find any examples. How could this be achieved?
After some research we found this:
function createXPathFromElement(elm) {
var allNodes = document.getElementsByTagName('*');
for (segs = []; elm && elm.nodeType == 1; elm = elm.parentNode)
{
if (elm.hasAttribute('id')) {
var uniqueIdCount = 0;
for (var n=0;n < allNodes.length;n++) {
if (allNodes[n].hasAttribute('id') && allNodes[n].id == elm.id) uniqueIdCount++;
if (uniqueIdCount > 1) break;
};
if ( uniqueIdCount == 1) {
segs.unshift('id("' + elm.getAttribute('id') + '")');
return segs.join('/');
} else {
segs.unshift(elm.localName.toLowerCase() + '[#id="' + elm.getAttribute('id') + '"]');
}
} else if (elm.hasAttribute('class')) {
segs.unshift(elm.localName.toLowerCase() + '[#class="' + elm.getAttribute('class') + '"]');
} else {
for (i = 1, sib = elm.previousSibling; sib; sib = sib.previousSibling) {
if (sib.localName == elm.localName) i++;
};
segs.unshift(elm.localName.toLowerCase() + '[' + i + ']');
};
};
return segs.length ? '/' + segs.join('/') : null;
}
function lookupElementByXPath(path) {
var evaluator = new XPathEvaluator();
var result = evaluator.evaluate(path, document.documentElement, null,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return result.singleNodeValue;
}

Scroll Events in TouchGridPanel

I am building a mobile application using Sencha Touch 1.0. I need to display report, for that am using grid given by Ext.ux.TouchGridPanel.
It is working fine.
Where as I need to capture Scroll event in Ext.ux.TouchGridPanel.
I have added 'scroll' in bubble event of Dataview.
I am also trying to capture the event after the Dataview created.
But nothing seems to be working. Below is the code which I have changed.
Does anybody has any idea how to capture the start of scroll event?
Thanks in advance.
Ext.ux.TouchGridPanel = Ext.extend(Ext.Panel, {
layout: "fit",
multiSelect: false,
initComponent: function () {
var me = this;
me.items = me.dataview = me.buildDataView();
Ext.ux.TouchGridPanel.superclass.initComponent.call(me);
var store = me.store;
store.on("update", me.dispatchDataChanged, me);
var dataview = me.dataview;
dataview.on('scroll', me.startScroll);
},
dispatchDataChanged: function (store, rec, operation) {
var me = this;
me.fireEvent("storeupdate", store, rec, operation);
},
startScroll: function (scroller, offset) {
console.log('is this event captured???')
var me = this;
me.fireEvent("scroll", this.scroller, offset);
},
buildDataView: function () {
var me = this, colModel = me.colModel, colNum = me.getColNum(false), cellWidth = 100 / colNum,
colTpl = '<div class="x-grid-head">';
colTpl += '<thead><tr class="x-grid-header">';
for (var i = 0; i < colModel.length; i++) {
var col = colModel[i];
var width = (Ext.isDefined(col.width)) ? ("width =" + (col.width - 4) + "%") : '';
colTpl += '<th class="x-grid-cell" ' + width + ' style="' + col.style + '" >' + col.header + '</th>';
}
colTpl += '</tr></thead>';
colTpl += '<tbody ><tpl for="."><tr class="x-grid-row">';
for (var i = 0; i < colModel.length; i++) {
var col = colModel[i];
var width = (Ext.isDefined(col.width)) ? ("width =" + col.width + "%") : '';
colTpl += '<td class="x-grid-cell" style="' + col.style + '" >{' + col.mapping + '}</td>';
}
colTpl += '</tr></tpl></tbody>';
colTpl += '</table></div>'
return new Ext.DataView({
store: me.store,
itemSelector: "tr.x-grid-row",
simpleSelect: me.multiSelect,
scroll: me.scroll,
tpl: new Ext.XTemplate(colTpl,
{
isRowDirty: function (dirty, data) {
return dirty ? "x-grid-row-dirty" : "";
}
}
),
prepareData: function (data, index, record) {
var column,
i = 0,
ln = colModel.length;
var prepare_data = {};
prepare_data.dirtyFields = {};
for (; i < ln; i++) {
column = colModel[i];
if (typeof column.renderer === "function") {
prepare_data[column.mapping] = column.renderer.apply(me, [data[column.mapping], column, record, index]);
} else {
prepare_data[column.mapping] = data[column.mapping];
}
}
prepare_data.isDirty = record.dirty;
prepare_data.rowIndex = index;
return prepare_data;
},
bubbleEvents: [
"beforeselect",
"containertap",
"itemdoubletap",
"itemswipe",
"itemtap",
"selectionchange",
"scroll"
]
});
},
// #private
onScrollStart: function () {
console.log("Are you coming here");
var offset = this.scroller.getOffset();
this.closest = this.getClosestGroups(offset);
this.setActiveGroup(this.closest.current);
},
// #private
onScroll: function (scroller, pos, options) {
}
});
Ext.reg("touchgridpanel", Ext.ux.TouchGridPanel);
We can directly access dataview scroller and check event of the same.
For eg.
newGrid = new Ext.ux.TouchGridPanel({....
after creation of the Panel just access its dataview scroller
newGrid.dataview.scroller.on('scroll', scrollGrid1);
var scrollGrid1 = function(scroller, offsets){
console.log(' Grid scrolling with offset ' + offsets.x + ' & ' + offsets.y);
}

Twitter and jQuery , render tweeted links

I am using jquery ajax to pull from the twitter api, i'm sure there's a easy way, but I can't find it on how to get the "tweet" to render any links that were tweeted to appear as a link. Right now it's only text.
$.ajax({
type : 'GET',
dataType : 'jsonp',
url : 'http://search.twitter.com/search.json?q=nettuts&rpp=2',
success : function(tweets) {
var twitter = $.map(tweets.results, function(obj, index) {
return {
username : obj.from_user,
tweet : obj.text,
imgSource : obj.profile_image_url,
geo : obj.geo
};
});
UPDATE:
The following function (plugin) works perfectly.
(function($) {
$.socialFader = function(options) {
var settings = {
tweetHolder : null,
tweetCount : 100,
fadeSpeed : 500,
tweetName: 'jquery'
};
if (options) {
$.extend(settings, options);
};
var URL = "http://search.twitter.com/search.json?q="+settings.tweetName+"&rpp=" + settings.tweetCount + "&callback=?";
function relative_time(time_value) {
var values = time_value.split(" ");
time_value = values[1] + " " + values[2] + ", " + values[5] + " " + values[3];
var parsed_date = Date.parse(time_value);
var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);
delta = delta + (relative_to.getTimezoneOffset() * 60);
var r = '';
if (delta < 60) {
r = 'a minute ago';
} else if(delta < 120) {
r = 'couple of minutes ago';
} else if(delta < (45*60)) {
r = (parseInt(delta / 60)).toString() + ' minutes ago';
} else if(delta < (90*60)) {
r = 'an hour ago';
} else if(delta < (24*60*60)) {
r = '' + (parseInt(delta / 3600)).toString() + ' hours ago';
} else if(delta < (48*60*60)) {
r = '1 day ago';
} else {
r = (parseInt(delta / 86400)).toString() + ' days ago';
}
return r;
};
String.prototype.hashify = function() {
return this.replace(/#([A-Za-z0-9\/\.]*)/g, function(m) {
return '<a target="_new" href="http://twitter.com/search?q=' + m.replace('#','') + '">' + m + "</a>";
});
};
String.prototype.linkify = function(){
return this.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/, function(m) {
return m.link(m);
});
};
String.prototype.atify = function() {
return this.replace(/#[\w]+/g, function(m) {
return '' + m + "";
});
};
$.getJSON(URL, function(JSON) {
$.each(JSON.results, function(i, tweet) {
var profilePicture = tweet.profile_image_url;
var userLink = tweet.from_user;
var text = tweet.text;
text = text.linkify().atify().hashify();
var createdAt = new Date(tweet.created_at);
var myTweet = '' + userLink + ' ';
myTweet += text;
$(settings.tweetHolder).append('<li class="cycles">' + myTweet + '</li>');
});
var elements = $(settings.tweetHolder).children();
var timeOutStart = 5000;
function fader(elementId) {
setTimeout(function() {
$(elements[elementId]).fadeOut(settings.fadeSpeed, function() {
$(elements[elementId + 1]).fadeIn(settings.fadeSpeed);
});
}, timeOutStart * (elementId));
};
for (var j = 0; j < elements.length; j++) {
fader(j);
};
});
};
})(jQuery);
Within my Ready Statement :
$.socialFader({ tweetHolder:"#twitter", tweetName:"nettuts", tweetCount:2 });
Here is a plugin I wrote which really simplifies the tweet/json aggregation then parsing. It fades the tweets in and out. Just grab the needed code. Enjoy.
(function($) {
$.socialFader = function(options) {
var settings = {
tweetHolder : null,
tweetCount : 99,
fadeSpeed : 500,
};
if (options) {
$.extend(settings, options);
};
var URL = "http://search.twitter.com/search.json?q=jquery&rpp=" + settings.tweetCount + "&callback=?";
function relative_time(time_value) {
var values = time_value.split(" ");
time_value = values[1] + " " + values[2] + ", " + values[5] + " " + values[3];
var parsed_date = Date.parse(time_value);
var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);
delta = delta + (relative_to.getTimezoneOffset() * 60);
var r = '';
if (delta < 60) {
r = 'a minute ago';
} else if(delta < 120) {
r = 'couple of minutes ago';
} else if(delta < (45*60)) {
r = (parseInt(delta / 60)).toString() + ' minutes ago';
} else if(delta < (90*60)) {
r = 'an hour ago';
} else if(delta < (24*60*60)) {
r = '' + (parseInt(delta / 3600)).toString() + ' hours ago';
} else if(delta < (48*60*60)) {
r = '1 day ago';
} else {
r = (parseInt(delta / 86400)).toString() + ' days ago';
}
return r;
};
String.prototype.hashify = function() {
return this.replace(/#([A-Za-z0-9\/\.]*)/g, function(m) {
return '<a target="_new" href="http://twitter.com/search?q=' + m.replace('#','') + '">' + m + "</a>";
});
};
String.prototype.linkify = function(){
return this.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/, function(m) {
return m.link(m);
});
};
String.prototype.atify = function() {
return this.replace(/#[\w]+/g, function(m) {
return '' + m + "";
});
};
$.getJSON(URL, function(JSON) {
$.each(JSON.results, function(i, tweet) {
var profilePicture = tweet.profile_image_url;
var userLink = tweet.from_user;
var text = tweet.text;
text = text.linkify().atify().hashify();
var createdAt = new Date(tweet.created_at);
var myTweet = '' + userLink + ' ';
myTweet += text;
$(settings.tweetHolder).append('<li class="cycles">' + myTweet + '</li>');
});
var elements = $(settings.tweetHolder).children();
var timeOutStart = 5000;
function fader(elementId) {
setTimeout(function() {
$(elements[elementId]).fadeOut(settings.fadeSpeed, function() {
$(elements[elementId + 1]).fadeIn(settings.fadeSpeed);
});
}, timeOutStart * (elementId));
};
for (var j = 0; j < elements.length; j++) {
fader(j);
};
});
};
})(jQuery);
You need to parse the tweet content, find the urls and then put them in between yourself.
Unfortunately, at the moment, the search API doesn't have the facility to break out tweet entities (i.e., links, mentions, hashtags) like some of the REST API methods. So, you could either parse out the entities yourself (I use regular expressions) or call back into the rest API to get the entities.
If you decide to call back into the REST API, and once you have extracted the status ID from the search API results, you would make a call to statuses/show like the following:
http://api.twitter.com/1/statuses/show/60183527282577408.json?include_entities=true
In the resultant JSON, notice the entities object.
"entities":{"urls":[{"expanded_url":null,"indices":[68,88],"url":"http:\/\/bit.ly\/gWZmaJ"}],"user_mentions":[],"hashtags":[{"text":"wordpress","indices":[89,99]}]}
You can use the above to locate the specific entities in the tweet (which occur between the string positions denoted by the indices property) and transform them appropriately.
If you prefer to parse the entities yourself, here are the (.NET Framework) regular expressions I use:
Link Match Pattern
(?:<\w+.*?>|[^=!:'"/]|^)((?:https?://|www\.)[-\w]+(?:\.[-\w]+)*(?::\d+)?(?:/(?:(?:[~\w\+%-]|(?:[,.;#:][^\s$]))+)?)*(?:\?[\w\+%&=.;:-]+)?(?:\#[\w\-\.]*)?)(?:\p{P}|\s|<|$)
Mention Match Pattern
\B#([\w\d_]+)
Hashtag Match Pattern
(?:(?:^#|[\s\(\[]#(?!\d\s))(\w+(?:[_\-\.\+\/]\w+)*)+)
Twitter also provides an open source library that helps capture Twitter-specific entities like links, mentions and hashtags. This java file contains the code defining the regular expressions that Twitter uses, and this yml file contains test strings and expected outcomes of many unit tests that exercise the regular expressions in the Twitter library.
How you process the tweet is up to you, however I process a copy of the original tweet, and pull all the links first, replacing them in the copy with spaces (so as not to modify the string length.) I capture the start and end locations of the match in the string, along with the matched content. I then pull mentions, then hashtags -- again replacing them in the tweet copy with spaces.
This approach ensures that I don't find false positives for mentions and hashtags in any links in the tweet.
I slightly modified previous one. Nothing lefts after all tweets disappears one by one.
Now it checks if there is any visible tweets and then refreshes tweets.
(function($) {
$.socialFader = function(options) {
var settings = {
tweetHolder : null,
tweetCount : 99,
fadeSpeed : 500,
};
if (options) {
$.extend(settings, options);
};
var URL = "http://search.twitter.com/search.json?q=istanbul&rpp=" + settings.tweetCount + "&callback=?";
function relative_time(time_value) {
var values = time_value.split(" ");
time_value = values[1] + " " + values[2] + ", " + values[5] + " " + values[3];
var parsed_date = Date.parse(time_value);
var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);
delta = delta + (relative_to.getTimezoneOffset() * 60);
var r = '';
if (delta < 60) {
r = 'a minute ago';
} else if(delta < 120) {
r = 'couple of minutes ago';
} else if(delta < (45*60)) {
r = (parseInt(delta / 60)).toString() + ' minutes ago';
} else if(delta < (90*60)) {
r = 'an hour ago';
} else if(delta < (24*60*60)) {
r = '' + (parseInt(delta / 3600)).toString() + ' hours ago';
} else if(delta < (48*60*60)) {
r = '1 day ago';
} else {
r = (parseInt(delta / 86400)).toString() + ' days ago';
}
return r;
};
String.prototype.hashify = function() {
return this.replace(/#([A-Za-z0-9\/\.]*)/g, function(m) {
return '<a target="_new" href="http://twitter.com/search?q=' + m.replace('#','') + '">' + m + "</a>";
});
};
String.prototype.linkify = function(){
return this.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/, function(m) {
return m.link(m);
});
};
String.prototype.atify = function() {
return this.replace(/#[\w]+/g, function(m) {
return '' + m + "";
});
};
$.getJSON(URL, function(JSON) {
$(settings.tweetHolder).find('li.cycles').remove();
$.each(JSON.results, function(i, tweet) {
var profilePicture = tweet.profile_image_url;
var userLink = tweet.from_user;
var text = tweet.text;
text = text.linkify().atify().hashify();
var createdAt = new Date(tweet.created_at);
var myTweet = '' + userLink + ' ';
myTweet += text;
$(settings.tweetHolder).append('<li class="cycles">' + myTweet + '</li>');
});
var elements = $(settings.tweetHolder).children();
var timeOutStart = 5000;
function fader(elementId) {
setTimeout(function() {
$(elements[elementId]).fadeOut(settings.fadeSpeed, function() {
$(elements[elementId + 1]).fadeIn(settings.fadeSpeed);
});
if (jQuery('#twitter ul li.cycles:visible').length==1) {
jQuery.socialFader({ tweetHolder:"#twitter ul", tweetCount:5 });
}
}, timeOutStart * (elementId));
};
for (var j = 0; j < elements.length; j++) {
fader(j);
};
});
};
})(jQuery);
jQuery(document).ready(function(){
jQuery.socialFader({ tweetHolder:"#twitter ul", tweetCount:5 });
});

Resources