[ PROGRAM ]

JavaScript

JavaScript プログラミング

2013/08/21 Tomohiro Kumagai
Update 2013/08/26 Tomohiro Kumagai

□ ツイッターアカウントをシェアするブックマークレット

ツイッターへのリンクを簡単に貼りたいなと思って、それを簡単に生成できるブックマークレットを作成しました。

EzTwitterShareLink ブックマークレット

今回作成したEzTwitterShareLinkブックマークレットをTwitter公式ページで使うと、そのページのアカウントへのリンクを簡単に作成できます。

たとえば @EasyStyleGK のリンクを作成したとき、次のようなものができます。

 

スタイルなし
標準スタイル

標準スタイルは EzTwitterShareLink.v2.css を使用しています。

このスタイルシートを自分のスタイルシートにコピーすると、EzTwitterShareLink で取得した HTML を標準スタイルの見た目で使えます。

EzTwitterShareLink の対応環境

主に Safari で動作チェックを行ってますが Google Chrome でも動くと思います。

Safari を使って、PC 用のツイッターページ (twitter.com) と iPhone 用のツイッターページ (mobile.twitter.com) の両方で動作するように作成しています。

もし動かないようでしたら @EasyStyleGK までお知らせ頂けると助かります。

EzTwitterShareLink を登録する

EzTwitterShareLink をブックマークに登録するには、次のようにします。

  1. ブックマークレットのソースコードをコピーします。
  2. このページをブックマークに登録します。
  3. 登録したブックマークの URL を編集し、コピーしたソースコードを貼り付けます。

このような流れでブックマークレットを登録します。

カスタマイズとソースコードの取得

次の【ソースコードをポップアップ】ボタンを押すと、ソースコードが表示されます。

必要な項目を選択したり、敬称を変更したり、各タグに設定するクラスのプレフィックスを変更したりすると、それに合わせたブックマークレットのソースコードが取得できます。

敬称やサフィックス HTML には引用符などの特殊文字も使用できます。

EzTwitterShareLink メーカー

Class プレフィックス 敬称
必要な項目
画像サイズ
<a> に含める表示名 @ @
サフィックス HTML

 

ユーザー名とスクリーン名について

Twitter では「ユーザー名」「スクリーン名」の呼び名がまちまちみたいで分りにくいですが、ここで言う「ユーザー名」は【熊谷友宏】とかで、「スクリーン名」は【@EasyStyleGK】とかを指しています。

Class プレフィックスについて

カスタマイズでClass プレフィックスを変更すると、ブックマークレットが生成する HTML の各タグのクラス名がそれから始まる名前に置き換えられます。

スタイルシートではクラス名ごとにデザインを適用できるので、たとえば画像ありとなしとか、小さい画像と大きい画像とか、カスタマイズ毎に Class プレフィックスを変更しておくと、それぞれ毎にスタイルを調整しやすくなります。

サフィックス HTML について

カスタマイズでサフィックス HTMLを変更すると、生成する HTML の表示名の次に、ここで指定した HTML が挿入されます。

ちなみにこのサフィックス HTML は、ツイッターリンクが貼られた<a> タグの外に追加されます。

 

HTML マークアップを自由に利用できるので、たとえば外部リンクを示すアイコン画像を表示したいときとかに重宝するかもしれません。

外部リンクのマークを含める方法は他にも、スタイルシートの:afterを使う方法もあります。

EzTwitterShareLink の使い方

後に掲載するEzTwitterShareLinkのソースコードをブックマークに登録して、リンクを貼りたい人の Twitter 公式ページでブックマークを呼び出します。

そうすると、そのページのリンク用 HTML がポップアップで表示されます。

EzTwitterShareLink でツイッターリンクを生成しました。

これを HTML ソースとして貼りつければ、その Twitter ページへのリンクがそこに貼られます。

デザインの調整方法

EzTwitterShareLink ではスタイルシートを使ってレイアウトを調整します。

標準スタイル EzTwitterShareLink.v2.css の内容をご自分のスタイルシートにコピーすると調整しやすいと思います。

2013.08.26 からはそれ以前のバージョンと生成されるタグの構造が僅かに違うため、どちらでも混在させられるように、"ez-twitter-share" と "ez-twitter-share-suffix" 以外のクラス名が変更になっています。

それ以前から利用されている方は、スタイルシートの再調整をお願いします。

生成される HTML の構成

HTML の構成は次のようになっています。

以下の例はClass プレフィックスが "ez-twitter-" の場合です。それ以外の Class プレフィックスに変更すると、その部分が指定したものに置き換わります。

<span class="ez-twitter-share">

<span class="ez-twitter-image">

<a href="ツイッターページの URL" target="_blank">

<img src="プロフィール画像">

</a>

</span>

<span class="ez-twitter-name">

<a href="ツイッターページの URL" target="_blank">

<span class="ez-twitter-username">ユーザー名</span>

<span class="ez-twitter-parenthesis-left">(</span>

<span class="ez-twitter-screenname">@スクリーン名</span>

<span class="ez-twitter-parenthesis-right">)</span>

<span class="ez-twitter-nametitle">さん</span>

</a>

</span>

<span class="ez-twitter-share-suffix"></span>

</span>

要素ひとつひとつに <span> タグがクラス名付きで設定されているので、これらのスタイルを調整すれば、かなり自由にレイアウトを調整できると思います。

 

たとえば少し強引ですけど、もしスクリーン名が不要な場合は "ez-twitter-profile-screenname" クラスを "display: none" に設定することで、非表示にもできたりします。

不必要な項目を含めないようにするのは、ブックマークレットの呼び出し引数を調整することでも可能です。詳細はソースコードのところで説明します。

EzTwitterShareLink のソースコード

javascript:

(

/* EzTwitterShareLink 2013.08.26.000 */
function(outputScheme, classPrefix, escapedProfileNameTitle, includeImage, includeUserName, includeScreenName, profileImageSize, escapedSuffixHtml, includesNameInAnchor)
{
	var rootNodeClassName = classPrefix + 'share';
	var profileImageNodeClassName = classPrefix + 'image';
	var profileDisplayNodeClassName = classPrefix + 'name';
	var profileUserNameNodeClassName = classPrefix + 'username';
	var profileScreenNameNodeClassName = classPrefix + 'screenname';
	var profileNameTitleNodeClassName = classPrefix + 'nametitle';
	var suffixNodeClassName = classPrefix + 'share-suffix';
	var profileParenthesisNodeClassPrefix = classPrefix + 'parenthesis-';

	var twitterSiteKindWeb = 1;
	var twitterSiteKindMobile = 2;

	/* documentNode の既定値は document とします。*/
	var documentNode = (documentNode ? documentNode : document);
	
	/* 敬称を取得する関数を定義します。*/
	function getNameTitle()
	{
		return unescape(escapedProfileNameTitle);
	}

	/* 末尾に付加するテキストを返す関数を定義します。*/
	function getSuffixText()
	{
		return (escapedSuffixHtml ? unescape(escapedSuffixHtml) : '');
	}

	/* 表示中のサイトの種類を取得する関数を定義します。*/
	function getTwitterSiteKind()
	{
		var type;
		
		var regex = new RegExp('^https?:\/\/([^\/]+).*$', 'i');
		var domain = documentNode.URL.replace(regex, '$1');

		switch (domain.toLowerCase())
		{
			case 'mobile.twitter.com':
				type = twitterSiteKindMobile;
				break;

			default:
				type = twitterSiteKindWeb;
				break;
		}

		return type;
	}

	/* 対象となるページのルートノードを取得する関数を定義します。*/
	function getContainerNodeForWeb()
	{
		return documentNode.getElementById('page-container');
	}

	function getContainerNodeForMobile()
	{
		return documentNode.getElementById('views');
	}

	function getContainerNode()
	{
		switch (getTwitterSiteKind())
		{
			case twitterSiteKindWeb:
				return getContainerNodeForWeb();

			case twitterSiteKindMobile:
				return getContainerNodeForMobile();
		}
	}

	/* 公式ツイッターページから画像の URL を取得する関数を定義します。*/
	function getImageUrl(sizeText)
	{
		switch (getTwitterSiteKind())
		{
			case twitterSiteKindWeb:
				return getImageUrlForWeb(sizeText);

			case twitterSiteKindMobile:
				return getImageUrlForMobile(sizeText);
		}
	}
	
	function convertImageUrlWithSize(imageUrl, sizeText)
	{
		if (imageUrl)
		{
			var regex = new RegExp('(|_mini|_normal|_bigger|_reasonably_small)\\.(\\w+)$');
			var suffix = (sizeText ? ('_' + sizeText) : '') + '.$2';
		
			return imageUrl.replace(regex, suffix);
		}
		else
		{
			return imageUrl;
		}
	}

	function getImageUrlForWeb(sizeText)
	{
		var imageUrl = null;
		var imageNodes = documentNode.getElementsByTagName('img');
    
		for (var index = 0; index < imageNodes.length; ++index)
		{
			var imageNode = imageNodes[index];

			if (imageNode.className == 'avatar size73')
			{
				imageUrl = convertImageUrlWithSize(imageNode.src, sizeText);
				break;
			}
		}

		return imageUrl;
	}

	function getImageUrlForMobile(sizeText)
	{
		var imageUrl = null;
		var imageNodes = documentNode.getElementsByTagName('img');
    
		for (var index = 0; index < imageNodes.length; ++index)
		{
			var imageNode = imageNodes[index];

			if (imageNode.className == 'avatar activeLink')
			{
				imageUrl = convertImageUrlWithSize(imageNode.src, sizeText);
				break;
			}
		}

		return imageUrl;
	}

	/* 公式ツイッターページからユーザー名を取得する関数を定義します。*/
	function getUserName()
	{
		switch (getTwitterSiteKind())
		{
			case twitterSiteKindWeb:
				return getUserNameForWeb();

			case twitterSiteKindMobile:
				return getUserNameForMobile();
		}
	}

	function getUserNameForWeb()
	{
		var userName = null;

		var containerNode = documentNode.getElementById('page-container');
		var spanNodes = containerNode.getElementsByTagName('span');

		for (var index = 0; index < spanNodes.length; ++index)
		{
			var spanNode = spanNodes[index];

			if (spanNode.className == 'profile-field')
			{
				userName = spanNode.innerText;
				break;
			}
		}

		return userName;
	}

	function getUserNameForMobile()
	{
		var userName = null;

		var nodes = documentNode.getElementsByTagName('h2');

		for (var index = 0; index < nodes.length; ++index)
		{
			var node = nodes[index];

			if (node.className == 'full-name-h2 userselect')
			{
				userName = node.innerText.replace(/^\s+|\s+$/g, "");
				break;
			}
		}

		return userName;
	}

	/* 公式ツイッターページからスクリーン名を取得する関数を定義します。*/
	function getScreenName()
	{
		switch (getTwitterSiteKind())
		{
			case twitterSiteKindWeb:
				return getScreenNameForWeb();

			case twitterSiteKindMobile:
				return getScreenNameForMobile();
		}
	}

	function getScreenNameForWeb()
	{
		var screenName = null;

		var containerNode = documentNode.getElementById('page-container');
		var divNodes = containerNode.getElementsByTagName('div');

		for (var index = 0; index < divNodes.length; ++index)
		{
			var divNode = divNodes[index];

			if (divNode.className == 'profile-card-inner')
			{
				screenName = divNode.getAttribute('data-screen-name');

				if (screenName)
				{
					 screenName = '@' + screenName;
				}

				break;
			}
		}

		return screenName;
	}

	function getScreenNameForMobile()
	{
		var screenName = null;

		var containerNode = documentNode.getElementById('views');
		var divNodes = containerNode.getElementsByTagName('div');

		for (var index = 0; index < divNodes.length; ++index)
		{
			var divNode = divNodes[index];

			if (divNode.className == 'screen-name userselect')
			{
				/* 見つかった div の中のタグではない部分のテキストを取得します。*/
				var name = '';

				for (var childIndex = 0; childIndex < divNode.childNodes.length; ++childIndex)
				{
					 var childNode = divNode.childNodes[childIndex];

					 if (childNode.nodeType == Node.TEXT_NODE)
					 {
					 	name += childNode.nodeValue;
					 }
				}

				screenName = '@' + name.replace(/^\s+|\s+$/g, "");

				break;
			}
		}

		return screenName;
	}

	/* 公式ツイッターページへのアンカータグを生成する関数を定義します。*/
	function createAnchorNodeForTwitterPage()
	{
		var node = documentNode.createElement('a');

		node.href = documentNode.URL.replace('mobile.twitter.com', 'twitter.com');
		node.target = '_blank';

		return node;
	}

	/* プロフィール画像タグを生成する関数を定義します。*/
	function createProfileImageNode()
	{
		var node = documentNode.createElement('span');

		var anchorNode = createAnchorNodeForTwitterPage();
		var imageNode = documentNode.createElement('img');

		node.className = profileImageNodeClassName;

		imageNode.src = getImageUrl(profileImageSize);

		node.appendChild(anchorNode);
		anchorNode.appendChild(imageNode);

		return node;
	}

	/* 括弧ノードを出力します。*/
	function createParenthesisNode(symbol, forLeft)
	{
		var node = document.createElement('span');
		
		node.className = profileParenthesisNodeClassPrefix + (forLeft ? 'left' : 'right');
		node.appendChild(document.createTextNode(symbol));
		
		return node;
	}
	
	/*
		指定したノードの前後にテキストノードを付加します。
		前後にノードを追加できるように、targetNode は親ノードを持つ必要があります。
	*/
	function appendParenthesesOnTargetTag(targetNode, prefix, suffix)
	{
		var nextNode = targetNode.nextSibling;
		
		var prefixNode = createParenthesisNode(prefix, true);
		var suffixNode = createParenthesisNode(suffix, false);
		
		targetNode.parentNode.insertBefore(prefixNode, targetNode);
		
		if (nextNode)
		{
			targetNode.parentNode.insertBefore(suffixNode, nextNode);
		}
		else
		{
			targetNode.parentNode.appendChild(suffixNode);
		}
	}
	
	/* 名前を表示するノードを生成する関数を定義します。*/
	function createDisplayNameNode()
	{
		var node = documentNode.createElement('span');

		var anchorFragment = document.createDocumentFragment();
		var anchorBeforeFragment = document.createDocumentFragment();
		var anchorAfterFragment = document.createDocumentFragment();

		var anchorNode = createAnchorNodeForTwitterPage();
		
		var targetForUserName;
		var targetForScreenName;
		var targetForNameTitle;
		
		/* 括弧表示を親ノードの前後に直接つける場合に true とします。*/
		var addScreenNameParenthesesOnParentNode;
		
		/* アンカーの外に括弧を出せるようにフラグメントに設定します。*/
		anchorFragment.appendChild(anchorNode);
		
		/* 追加位置を決定します。*/
		switch (includesNameInAnchor)
		{
			case 'user':
				targetForUserName = anchorNode;
				targetForScreenName = anchorAfterFragment;
				targetForNameTitle = anchorAfterFragment;
				addScreenNameParenthesesOnParentNode = false;
				break;
				
			case 'screen':
				targetForUserName = anchorBeforeFragment;
				targetForScreenName = anchorNode;
				targetForNameTitle = anchorAfterFragment;
				addScreenNameParenthesesOnParentNode = true;
				break;
				
			case 'name':
				targetForUserName = anchorNode;
				targetForScreenName = anchorNode;
				targetForNameTitle = anchorAfterFragment;
				addScreenNameParenthesesOnParentNode = false;
				break;
			
			case 'none':
				targetForUserName = anchorBeforeFragment;
				targetForScreenName = anchorBeforeFragment;
				targetForNameTitle = anchorBeforeFragment;
				addScreenNameParenthesesOnParentNode = false;
				break;
			
			default:
				targetForUserName = anchorNode;
				targetForScreenName = anchorNode;
				targetForNameTitle = anchorNode;
				addScreenNameParenthesesOnParentNode = false;
				break;
		}

		/* 表示名を追加します。*/		
		if (includeUserName)
		{
			var userNameNode = documentNode.createElement('span');
			var userNameTextNode = documentNode.createTextNode(getUserName());
			
			userNameNode.className = profileUserNameNodeClassName;
			userNameNode.appendChild(userNameTextNode);

			targetForUserName.appendChild(userNameNode);
		}
		
		if (includeScreenName)
		{
			var screenNameNode = documentNode.createElement('span');
			var screenNameTextNode = documentNode.createTextNode(getScreenName());

			screenNameNode.className = profileScreenNameNodeClassName;
			screenNameNode.appendChild(screenNameTextNode);
		
			targetForScreenName.appendChild(screenNameNode);
			
			if (includeUserName)
			{
				/* 親フラグメントがターゲットの場合は、そこに追加したテキストを追加の基準にします。*/
				appendParenthesesOnTargetTag((addScreenNameParenthesesOnParentNode ? screenNameNode.parentNode : screenNameNode), '(', ')');
			}
		}

		if (includeUserName || includeScreenName)
		{
			var nameTitleNode = documentNode.createElement('span');
			var nameTitleTextNode = documentNode.createTextNode(getNameTitle());
			
			nameTitleNode.className = profileNameTitleNodeClassName;
			nameTitleNode.appendChild(nameTitleTextNode);
		
			targetForNameTitle.appendChild(nameTitleNode);
		}
		
		/* 表示名ノードを完成させます。*/
		node.className = profileDisplayNodeClassName;

		if (anchorBeforeFragment.hasChildNodes()) node.appendChild(anchorBeforeFragment);
		if (anchorNode.hasChildNodes()) node.appendChild(anchorFragment);
		if (anchorAfterFragment.hasChildNodes()) node.appendChild(anchorAfterFragment);

		return node;
	}

	/* 最後に付与するタグを内部に持ったノードを生成する関数を定義します。*/
	function createSuffixNode()
	{
		var node = documentNode.createElement('span');

		node.className = suffixNodeClassName;
		node.innerHTML = getSuffixText();

		return node;
	}

	/* 指定されたノードの内容を HTML として画面に表示する関数です。*/
	function showHtmlWithNode(node)
	{
		prompt('EzTwitterShareLink', node.outerHTML);
	}
	
	/* 表示名を含めるべきかを判定する関数です。*/
	function isIncludeDisplayNameNode()
	{
		return (includeUserName || includeScreenName);
	}

	/* サフィックスノードを含めるべきかを判定する関数です。*/
	function isIncludeSuffixNode()
	{
		return (escapedSuffixHtml ? true : false);
	}

	/* ツイッターページからリンク用の HTML を生成します。*/
	var rootNode = documentNode.createElement('span');

	rootNode.className = rootNodeClassName;

	if (includeImage) rootNode.appendChild(createProfileImageNode());
	if (isIncludeDisplayNameNode()) rootNode.appendChild(createDisplayNameNode());
	if (isIncludeSuffixNode()) rootNode.appendChild(createSuffixNode());

	/* 必要に応じて HTML を出力します。 */
	switch (outputScheme)
	{
		case 'alert':
			showHtmlWithNode(rootNode);
			break;
	}
 
	return rootNode;
}

)

('alert', 'ez-twitter-', 'さん', true, true, true, 'normal', '', 'all');

留意事項

このソースコードをコピーしてブックマークに貼りつけてもブックマークレットとして使用できますが、敬称サフィックス HTMLなどをカスタマイズしている場合は、全体を encodeURL 関数でエンコードしたものを張り付けないと、特殊文字の都合で正しく動作しない場合があります。

カスタマイズ操作を行った場合は、カスタマイズとソースコードの取得 のボタンを使ってソースコードを取得するのが安全です。

呼び出し引数

ブックマークレットのコードの最後で渡している呼び出し引数の意味は次のようになっています。

これを調整することで、JavaScript の知識がほとんどなくても、取得できる HTML をカスタマイズできます。

@1 outputScheme HTML の出力方法を指定します。現在は必ず 'alert' を指定するようにします。
@2 classPrefix 各ノードに設定するクラスのプレフィックスを文字列で指定します。
@3 escapedProfileNameTitle 表示名の末尾に付与する敬称や肩書を文字列で指定します。値は escape 関数を使ってエンコードしたものを渡します。
@4 includeImage プロフィール画像を含めるかどうかを true または false で指定します。
@5 includeUserName ユーザー名を含めるかどうかを true または false で指定します。
@6 includeScreenName @スクリーン名を含めるかどうかを true または false で指定します。
@7 profileImageSize プロフィール画像のサイズを 'normal', 'mini', 'bigger' または ''(オリジナルサイズ)で指定します。
@8 escapedSuffixHtml サフィックスとして使う HTML を指定します。値は escape 関数を使ってエンコードしたものを渡します。
@9 includesNameInAnchor 表示名のどの部分を <a> に含めるかを指定します。'all', 'user', 'screen', 'name', 'none' から指定できます。

更新履歴

2013.08.26
  1. @スクリーン名の前後に付加する ( ) をアンカーの外に移動しました。
  2. タグのクラス名が "ez-twitter-share" と "ez-twitter-share-suffix" を除き、変更になっています。
2013.08.24
  1. <a> タグに含める表示名をいくつかのパターンから指定できるようになりました。
2013.08.23
  1. 敬称に引用符などの特殊文字が使えるようになりました。
  2. サフィックス HTML を指定できるようになりました。
2013.08.22
  1. 呼び出し引数でカスタマイズできるようになりました。カスタマイズできる項目は、クラス名のプレフィックス、敬称、プロフィール画像の有無、プロフィール画像のサイズ、ユーザー名の有無、@スクリーン名の有無です。
  2. サフィックスノードのクラス名が間違っていたのを修正しました。
2013.08.21
  1. 初回リリース版

[ もどる ]


 

カスタム検索

copyright © Tomohiro Kumagai @ EasyStyle G.K.
contact me: please from mail-form page.