Created
January 28, 2012 12:52
-
-
Save quietlynn/1694197 to your computer and use it in GitHub Desktop.
[DEPRECATED] Google+ Fuu
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Google+ Fuu => Reply posts in one click. | |
Copyright (C) 2012 Jingqin Lynn | |
Includes Japansese translation of UI text. | |
Copyright (C) 2012 +Cless Jiang < https://plus.google.com/114649741876253367865 > | |
This program is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation, either version 3 of the License, or | |
(at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this program. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
// ==UserScript== | |
// @name Google+ Fuu | |
// @namespace http://project.quietmusic.org/j/ | |
// @description Reply posts in one click. | |
// @match https://plus.google.com/* | |
// ==/UserScript== | |
(function() { | |
//Use the <base> element to detect Google+ main page. | |
var base = document.querySelector('base'); | |
if (!base) return; | |
if (!base.href.match(/^https:\/\/plus\.google\.com(\/u\/\d+)?\/?/)) return; | |
//Chrome V8 don't support unsafeWindow. Time for a hack. | |
if (window == unsafeWindow) { | |
var span = document.createElement('span'); | |
span.setAttribute('onclick', 'return window;'); | |
unsafeWindow = span.onclick(); | |
} | |
//For shorter code. | |
var win = unsafeWindow; | |
win.ext = win.ext || {}; | |
if (win.ext.fuu) { | |
return; | |
} else { | |
win.ext.fuu = {}; | |
} | |
var main = function($) { | |
var getSetting = function() { | |
var settings = null; | |
var settingStr = localStorage.getItem('fuu_settings'); | |
if(settingStr) { | |
settings = JSON.parse(settingStr); | |
} else { | |
settings = { | |
'selected' : 0, | |
'list' : [ | |
{ | |
'type' : 'text', | |
'value' : null //We'll fix this in getLocale. | |
} | |
] | |
}; | |
} | |
return settings; | |
}; | |
var saveSettings = function() { | |
localStorage.setItem('fuu_settings', JSON.stringify(settings)); | |
$('.ext-fuu-button').attr('title', settings.list[settings.selected].value); | |
menu.html(''); | |
buildMenu(); | |
}; | |
var getLocale = function(settings) { | |
var lang = { | |
'en': { | |
'fuuButtonText' : 'Fuu', | |
'fuuDefaultValue' : 'Fuu', | |
'add' : 'Add', | |
'edit' : 'Edit', | |
'delete' : 'Delete', | |
'input_reply_value' : 'Please input the $0 of the reply:', | |
'empty_toggle_html' : '(Leave empty to toggle HTML)', | |
'edit_prompt' : 'Please input the index of the item to edit:', | |
'delete_prompt' : 'Please input the index of the item to delete:', | |
'zero_based' : '(starting from 0)', | |
'delete_confirm' : 'Are you sure to delete this item?', | |
'delete_last' : 'The last item cannot be deleted.', | |
'html' : 'HTML code', | |
'text' : 'text', | |
'already_fuued' : 'Do you really want to reply the post again?', | |
'alert_range': 'Please input an integer from $0 to $1 inclusive.' | |
}, | |
'zh-hans': { | |
'fuuButtonText' : '呼', | |
'fuuDefaultValue' : '呼', | |
'add' : '添加', | |
'edit' : '编辑', | |
'delete' : '删除', | |
'input_reply_value' : '请输入自动回复的 $0 :', | |
'empty_toggle_html' : '(留空切换是否使用 HTML)', | |
'edit_prompt' : '请输入要编辑的选项编号', | |
'delete_prompt' : '请输入要删除的选项编号', | |
'zero_based' : '(从0开始)', | |
'delete_confirm' : '确定要删除这个选项吗?', | |
'delete_last' : '这已经是最后一个选项了,所以不能删除的说。', | |
'html' : 'HTML 代码', | |
'text' : '文本', | |
'already_fuued' : '这个贴子已经呼过了呢。还要继续吗?', | |
'alert_range': '请输入一个 $0 到 $1 之间的整数!' | |
}, | |
'zh-hant': { | |
'fuuButtonText' : '呼', | |
'fuuDefaultValue' : '呼', | |
'add' : '添加', | |
'edit' : '編輯', | |
'delete' : '刪除', | |
'input_reply_value' : '請輸入要回复的 $0 :', | |
'empty_toggle_html' : '(留空切換是否使用 HTML)', | |
'edit_prompt' : '請輸入要編輯的選項編號:', | |
'delete_prompt' : '請輸入要刪除的選項編號:', | |
'zero_based' : '(從0開始)', | |
'delete_confirm' : '確定要刪除這個選項嗎?', | |
'delete_last' : '這已經是最後一個選項了,所以不能刪除的说。', | |
'html' : 'HTML 代码', | |
'text' : '文本', | |
'already_fuued' : '這個貼子已經呼過了呢。還要繼續嗎?', | |
'alert_range': '請輸入一個 $0 到 $1 之間的整數!' | |
}, | |
'ja': { | |
'fuuButtonText' : 'ふぅ', | |
'fuuDefaultValue' : 'ふぅ', | |
'add' : '追加', | |
'edit' : '編集', | |
'delete' : '削除', | |
'input_reply_value' : '自動コメントの $0 を入力してください:', | |
'empty_toggle_html' : '(空にしてHTMLを使うかどうか切り替える)', | |
'edit_prompt' : '編集したい項目番号を入力してください', | |
'delete_prompt' : '削除したい項目番号を入力してください', | |
'zero_based' : '(0から)', | |
'delete_confirm' : 'この項目を削除してよろしいですか?', | |
'delete_last' : 'この項目は最後の項目です、削除できませんにゃ☆~', | |
'html' : 'HTML コード', | |
'text' : 'テキスト', | |
'already_fuued' : 'この投稿にはすでにコメントしたの、もう一回コメントしますか?', | |
'alert_range': ' $0 から $1 までの間の整数を入力してください!' | |
} | |
}; | |
//alias for languages | |
lang['zh-cn'] = lang['zh-hans']; | |
lang['zh-sg'] = lang['zh-hans']; | |
lang['zh-tw'] = lang['zh-hant']; | |
lang['zh-hk'] = lang['zh-hant']; | |
lang['zh'] = lang['zh-hans']; //fallback | |
lang[''] = lang['en']; //general fallback | |
var langCode = settings.lang || $.gplus.getLangCode(); | |
var loc = lang[langCode]; | |
if(!loc) { | |
loc = lang[langCode.substr(0,2)]; | |
if(!loc) { | |
loc = lang['']; | |
} | |
} | |
//Fix default rule | |
if(!settings.list[0].value) settings.list[0].value = loc['fuuDefaultValue']; | |
return loc; | |
}; | |
var onFuuClick = function (e) { | |
if(!e) e = event; | |
var button = $(e.target); | |
if(button.attr('data-ext-fuu-ed')) { | |
if(confirm(loc['already_fuued'])) { | |
button.attr('data-ext-fuu-ed', 'false'); | |
} else { | |
return; | |
} | |
} | |
var bar = button.parent(); | |
var post = button.parentsUntil('.Wbhcze').last(); | |
var fuuComplete = function() { | |
post.stopDynamicSelect(handler); | |
button.attr('data-ext-fuu-ed', 'true'); | |
}; | |
var handler = post.dynamicSelect('.g-z-e-Ia-df', function(ed) { | |
fuu(ed, post, fuuComplete); | |
}); | |
$.gplus.doClick($('.rBJ4nd', post)[0]); | |
}; | |
//Add fuu-text to the editor and then submit the reply. | |
var fuu = function (ed, post, callback) { | |
if(ed.children.length == 0) { | |
var onNodeInserted = function(e) { | |
if(!e) e = event; | |
ed.removeEventListener('DOMNodeInserted', onNodeInserted, false); | |
fuu(ed, post, callback); | |
} | |
ed.addEventListener('DOMNodeInserted', onNodeInserted, false); | |
return; | |
} | |
if(ed.children[0].tagName == 'DIV') { | |
if(callback) callback(); | |
var handler = $(ed).dynamicSelect('iframe', function (frame) { | |
console.log('#!frame'); | |
$(ed).stopDynamicSelect(handler); | |
//The content in the frame is changing, and DOMNodeInserted won't work. | |
//Use setInterval as a workaround. | |
var it = setInterval(function() { | |
if(frame.contentDocument.body.classList.contains('editable')) { | |
clearInterval(it); | |
fuu(frame.contentDocument.body, post); | |
} | |
}, 100); | |
}); | |
return; | |
} | |
if(callback) callback(); | |
ed.ownerDocument.body.scrollIntoView(ed); | |
var fuuItem = settings.list[settings.selected]; | |
switch (fuuItem.type) { | |
case 'html': | |
ed.innerHTML = fuuItem.value; | |
break; | |
case 'text': | |
ed.textContent = fuuItem.value; | |
break; | |
} | |
//"Input" something to enable the submit button. | |
$.gplus.doKeypress(ed); | |
var submit = $('.b-a-ga', post); | |
if(submit.attr('aria-disabled') != 'true') { | |
$.gplus.doClick(submit[0]); | |
} | |
else { | |
//Webkit don't support DOMAttrModified. | |
//Use setInterval as workaround. | |
//Todo: use DOMAttrModified on supported browsers such as Firefox. | |
var it = setInterval(function() { | |
if(submit.attr('aria-disabled') != 'true') { | |
clearInterval(it); | |
$.gplus.doClick(submit[0]); | |
}; | |
}, 100); | |
} | |
return true; | |
}; | |
//Menu | |
var createCmd = function (label) { | |
var cmd = $('<menuitem/>'); | |
cmd.attr('label', label); | |
cmd.css('cursor', 'pointer'); | |
cmd.click(fuuMenuClick); | |
menu.append(cmd); | |
return cmd; | |
}; | |
var buildMenu = function () { | |
for(var i = 0; i < settings.list.length; i++) { | |
var label = settings.selected == i ? '[*]' : '[ ]' | |
label += settings.list[i].value; | |
var cmd = createCmd(label); | |
cmd.attr('data-fuu-index', i.toString()); | |
} | |
var cmdAdd = createCmd('[+]' + loc['add']); | |
cmdAdd.attr('data-fuu-action', 'add'); | |
var cmdEdit = createCmd('[%]' + loc['edit']); | |
cmdEdit.attr('data-fuu-action', 'edit'); | |
var cmdDelete = createCmd('[X]' + loc['delete']); | |
cmdDelete.attr('data-fuu-action', 'delete'); | |
}; | |
var fuuMenuClick = function (e) { | |
if(!e) e = event; | |
if(typeof(document.body.contextMenu) == 'undefined') { | |
menu.hide(); | |
mask.hide(); | |
} | |
var action = e.target.getAttribute('data-fuu-action'); | |
if (action) { | |
switch(action) { | |
case 'add': | |
var fuu = inputFuu(); | |
if(!fuu) return; | |
settings.list.push(fuu); | |
settings.selected = settings.list.length - 1; | |
break; | |
case 'edit': | |
var input = prompt(loc['edit_prompt'] + loc['zero_based'], '0'); | |
if(!input) return; | |
var i = parseInt(input); | |
if(isNaN(i) || i < 0 || i >= settings.list.length) { | |
alert(loc['alert_range'].replace('$0', '0').replace('$1', settings.list.length - 1)); | |
return; | |
} | |
var fuu = inputFuu(settings.list[i]); | |
if(!fuu) return; | |
settings.list[i] = fuu; | |
settings.selected = i; | |
break; | |
case 'delete': | |
var input = prompt(loc['delete_prompt'] + loc['zero_based'], '0'); | |
if(!input) return; | |
var i = parseInt(input); | |
if(isNaN(i) || i < 0 || i >= settings.list.length) { | |
alert(loc['alert_range'].replace('$0', '0').replace('$1', settings.list.length - 1)); | |
return; | |
} | |
if(settings.list.length == 1) { | |
alert(loc['delete_last']); | |
return; | |
} | |
if(!confirm(loc['delete_confirm'] + '\r\n' + settings.list[i].value)) { | |
return; | |
} | |
if (settings.selected == i) { | |
settings.selected = 0; | |
} else if (settings.selected > i) { | |
settings.selected--; | |
} | |
for(var j = i; j < settings.list.length - 1; j++) { | |
settings.list[j] = settings.list[j+1]; | |
} | |
settings.list.pop(); | |
break; | |
} | |
saveSettings(); | |
} else { | |
var fuuIndex = parseInt(e.target.getAttribute('data-fuu-index')); | |
settings.selected = fuuIndex; | |
saveSettings(); | |
if(menuOrigin) { | |
$.gplus.doClick(menuOrigin); | |
} | |
} | |
}; | |
var menu = $('<menu/>'); | |
menu.attr('type', 'context').attr('id', 'ext-fuu-menu').css({ | |
'position' : 'absolute', | |
'display' : 'none', | |
'padding' : '0', | |
'margin' :'0', | |
'backgroundColor' : '#FBFBFB', | |
'borderStyle' : 'solid', | |
'borderColor' : '#E3E3E3', | |
'z-index': '9999', | |
}); | |
var menuOrigin = null; | |
menu[0].addEventListener('show', function(e) { | |
if(!e) e = event; | |
menuOrigin = e.explicitOriginalTarget.parentElement; | |
}); | |
menu.appendTo(document.body); | |
//Polyfill HTML5 context menu. | |
var mask = $('<div/>').css({ | |
'position' : 'fixed', | |
'width' : '100%', | |
'height' : '100%', | |
'left' : '0', | |
'top' : '0', | |
'display' : 'none' | |
}).click(function() { | |
menu.hide(); | |
mask.hide(); | |
}).appendTo(document.body); | |
var showFuuMenu = function (e) { | |
if(!e) e = event; | |
e.preventDefault(); | |
menuOrigin = e.target; | |
var button = $(e.target); | |
var pos = button.offset(); | |
menu.show(); | |
menu.offset({ | |
top: pos.top + button[0].offsetHeight, | |
left: pos.left | |
}); | |
$('menuitem', menu).each(function(_, cmd) { | |
cmd = $(cmd); | |
cmd.text(cmd.attr('label')); | |
cmd.css('display', 'block'); | |
}); | |
mask.show(); | |
}; | |
var inputFuu = function (fuu) { | |
if(!fuu) fuu = { 'value' : loc['fuuDefaultValue'], 'type' : 'text' }; | |
var result = null; | |
do { | |
result = win.prompt( | |
loc['input_reply_value'].replace('$0', loc[fuu.type]) + '\r\n' + loc['empty_toggle_html'], | |
fuu.value); | |
if(result == null) return null; | |
if(result.length > 0) break; | |
fuu.type = (fuu.type == 'text') ? 'html' : 'text'; | |
} while(true); | |
fuu.value = result; | |
return fuu; | |
}; | |
//Init | |
var settings = getSetting(); | |
var loc = getLocale(settings); | |
buildMenu(); | |
//Add "Fuu" button to the post toolbar. | |
$(document.body).dynamicSelect('.vo, .cWD3F', function(bar) { | |
bar = $(bar); | |
if (bar.attr('data-ext-fuu')) return; | |
if (!bar.hasClass('cWD3F')) bar.append(document.createTextNode(' - ')) | |
var button = $('<span/>'); | |
button.attr('role', 'button').attr('class', 'c-C ext-fuu-button'); | |
button.attr('title', settings.list[settings.selected].value).text(loc['fuuButtonText']); | |
var whiteSpaceDiv = $('.LwSBrb.Zey5Bf', bar); | |
if (whiteSpaceDiv.length > 0) { | |
whiteSpaceDiv.before(button); | |
} else { | |
bar.append(button); | |
} | |
button.click(onFuuClick); | |
var domButton = button[0]; | |
if(typeof(domButton.contextMenu) == 'undefined') { | |
button.on('contextmenu', showFuuMenu); | |
} else { | |
domButton.contextMenu = menu[0]; | |
button.attr('contextmenu', 'ext-fuu-menu'); | |
} | |
bar.attr('data-ext-fuu', 'buttonInserted'); | |
}); | |
}; | |
if(win.ext.toolkitReady) { | |
main(win.jQuery); | |
} else { | |
win.ext.toolkitCallback = win.ext.toolkitCallback || []; | |
win.ext.toolkitCallback.push(main); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment