<commit_before>frappe.provide('frappe.desktop');

frappe.pages['desktop'].on_page_load = function(wrapper) {

	// load desktop
	if(!frappe.list_desktop) {
		frappe.desktop.set_background();
	}
	frappe.desktop.refresh(wrapper);
};

frappe.pages['desktop'].on_page_show = function(wrapper) {
	if(frappe.list_desktop) {
		$("body").attr("data-route", "list-desktop");
	}
};

$.extend(frappe.desktop, {
	refresh: function(wrapper) {
		if (wrapper) {
			this.wrapper = $(wrapper);
		}

		this.render();
		this.make_sortable();
	},

	render: function() {
		var me = this;
		frappe.utils.set_title(__("Desktop"));

		var template = frappe.list_desktop ? "desktop_list_view" : "desktop_icon_grid";

		var all_icons = frappe.get_desktop_icons();
		var explore_icon = {
			module_name: 'Explore',
			label: 'Explore',
			_label: __('Explore'),
			_id: 'Explore',
			_doctype: '',
			icon: 'octicon octicon-telescope',
			color: '#7578f6',
			link: 'modules'
		};
		explore_icon.app_icon = frappe.ui.app_icon.get_html(explore_icon);
		all_icons.push(explore_icon);

		frappe.desktop.wrapper.html(frappe.render_template(template, {
			// all visible icons
			desktop_items: all_icons,
		}));

		frappe.desktop.setup_module_click();

		// notifications
		frappe.desktop.show_pending_notifications();
		$(document).on("notification-update", function() {
			me.show_pending_notifications();
		});

		$(document).trigger("desktop-render");

	},

	render_help_messages: function(help_messages) {
		var wrapper = frappe.desktop.wrapper.find('.help-message-wrapper');
		var $help_messages = wrapper.find('.help-messages');

		var set_current_message = function(idx) {
			idx = cint(idx);
			wrapper.current_message_idx = idx;
			wrapper.find('.left-arrow, .right-arrow').addClass('disabled');
			wrapper.find('.help-message-item').addClass('hidden');
			wrapper.find('[data-message-idx="'+idx+'"]').removeClass('hidden');
			if(idx > 0) {
				wrapper.find('.left-arrow').removeClass('disabled');
			}
			if(idx < help_messages.length - 1) {
				wrapper.find('.right-arrow').removeClass('disabled');
			}
		}

		if(help_messages) {
			wrapper.removeClass('hidden');
			help_messages.forEach(function(message, i) {
				var $message = $('

')
					.attr('data-message-idx', i)
					.html(frappe.render_template('desktop_help_message', message))
					.appendTo($help_messages);

			});

			set_current_message(0);

			wrapper.find('.close').on('click', function() {
				wrapper.addClass('hidden');
			});
		}

		wrapper.find('.left-arrow').on('click', function() {
			if(wrapper.current_message_idx) {
				set_current_message(wrapper.current_message_idx - 1);
			}
		})

		wrapper.find('.right-arrow').on('click', function() {
			if(help_messages.length > wrapper.current_message_idx + 1) {
				set_current_message(wrapper.current_message_idx + 1);
			}
		});

	},

	setup_module_click: function() {
		var wiggling = false; // wiggle, wiggle, wiggle.

		if(frappe.list_desktop) {
			frappe.desktop.wrapper.on("click", ".desktop-list-item", function() {
				frappe.desktop.open_module($(this));
			});
		} else {
			frappe.desktop.wrapper.on("click", ".app-icon", function() {
				if ( !wiggling ) {
					frappe.desktop.open_module($(this).parent());
				}
			});
		}
		frappe.desktop.wrapper.on("click", ".circle", function() {
			var doctype = $(this).attr('data-doctype');
			if(doctype) {
				frappe.ui.notifications.show_open_count_list(doctype);
			}
		});

		this.setup_wiggle()
	},

	setup_wiggle: () => {
		// Wiggle, Wiggle, Wiggle.
		const DURATION_LONG_PRESS = 1000;
		// lesser the antidode, more the wiggle (like your drunk uncle)
		// 75 seems good to replicate the iOS feels.
		const WIGGLE_ANTIDODE     = 75;

		var   timer_id      = 0;
		const $cases        = frappe.desktop.wrapper.find('.case-wrapper');
		const $icons        = frappe.desktop.wrapper.find('.app-icon');
		const $notis        = $(frappe.desktop.wrapper.find('.circle').toArray().filter((object) => {
			// This hack is so bad, I should punch myself.
			const doctype   = $(object).data('doctype');

			return doctype;
		}));

		const clearWiggle   = () => {
			const $closes   = $cases.find('.module-remove');
			$closes.hide();
			$notis.show();

			$icons.trigger('stopRumble');

			wiggling = false;
		};

		// initiate wiggling.
		$icons.jrumble({
			speed: WIGGLE_ANTIDODE // seems neat enough to match the iOS way
		});

		frappe.desktop.wrapper.on('mousedown', '.app-icon', () => {
			timer_id     = setTimeout(() => {
				wiggling = true;
				// hide all notifications.
				$notis.hide();

				$cases.each((i) => {
					const $case    = $($cases[i]);
					const template =
					`

									&times

					`;

					$case.append(template);
					const $close  = $case.find('.module-remove');
					const name    = $case.attr('title');
					$close.click(() => {
						// good enough to create dynamic dialogs?
						const dialog = new frappe.ui.Dialog({
							title: __(`Hide ${name}?`)
						});
						dialog.set_primary_action(__('Hide'), () => {
							frappe.call({
								method: 'frappe.desk.doctype.desktop_icon.desktop_icon.hide',
								args: { name: name },
								freeze: true,
								callback: (response) =>
								{
									if ( response.message ) {
										location.reload();
									}
								}
							})

							dialog.hide();

							clearWiggle();
						});
						// Hacks, Hacks and Hacks.
						var $cancel = dialog.get_close_btn();
						$cancel.click(() => {
							clearWiggle();
						});
						$cancel.html(__(`Cancel`));

						dialog.show();
					});
				});

				$icons.trigger('startRumble');
			}, DURATION_LONG_PRESS);
		});
		frappe.desktop.wrapper.on('mouseup mouseleave', '.app-icon', () => {
			clearTimeout(timer_id);
		});

		// also stop wiggling if clicked elsewhere.
		$('body').click((event) => {
			if ( wiggling ) {
				const $target = $(event.target);
				// our target shouldn't be .app-icons or .close
				const $parent = $target.parents('.case-wrapper');
				if ( $parent.length == 0 )
					clearWiggle();
			}
		});
		// end wiggle
	},

	open_module: function(parent) {
		var link = parent.attr("data-link");
		if(link) {
			if(link.indexOf('javascript:')===0) {
				eval(link.substr(11));
			} else if(link.substr(0, 1)==="/" || link.substr(0, 4)==="http") {
				window.open(link, "_blank");
			} else {
				frappe.set_route(link);
			}
			return false;
		} else {
			var module = frappe.get_module(parent.attr("data-name"));
			if (module && module.onclick) {
				module.onclick();
				return false;
			}
		}
	},

	make_sortable: function() {
		if (frappe.dom.is_touchscreen() || frappe.list_desktop) {
			return;
		}

		new Sortable($("#icon-grid").get(0), {
			onUpdate: function(event) {
				var new_order = [];
				$("#icon-grid .case-wrapper").each(function(i, e) {
					new_order.push($(this).attr("data-name"));
				});

				frappe.call({
					method: 'frappe.desk.doctype.desktop_icon.desktop_icon.set_order',
					args: {
						'new_order': new_order,
						'user': frappe.session.user
					},
					quiet: true
				});
			}
		});
	},

	set_background: function() {
		frappe.ui.set_user_background(frappe.boot.user.background_image, null,
			frappe.boot.user.background_style);
	},

	show_pending_notifications: function() {
		var modules_list = frappe.get_desktop_icons();
		for (var i=0, l=modules_list.length; i < l; i++) {
			var module = modules_list[i];

			var module_doctypes = frappe.boot.notification_info.module_doctypes[module.module_name];

			var sum = 0;
			if(module_doctypes) {
				if(frappe.boot.notification_info.open_count_doctype) {
					// sum all doctypes for a module
					for (var j=0, k=module_doctypes.length; j < k; j++) {
						var doctype = module_doctypes[j];
						sum += (frappe.boot.notification_info.open_count_doctype[doctype] || 0);
					}
				}
			} else if(frappe.boot.notification_info.open_count_doctype
				&& frappe.boot.notification_info.open_count_doctype[module.module_name]!=null) {
				// notification count explicitly for doctype
				sum = frappe.boot.notification_info.open_count_doctype[module.module_name];

			} else if(frappe.boot.notification_info.open_count_module
				&& frappe.boot.notification_info.open_count_module[module.module_name]!=null) {
				// notification count explicitly for module
				sum = frappe.boot.notification_info.open_count_module[module.module_name];
			}

			// if module found
			if(module._id.indexOf('/')===-1) {
				var notifier = $(".module-count-" + module._id);
				if(notifier.length) {
					notifier.toggle(sum ? true : false);
					var circle = notifier.find(".circle-text");
					var text = sum || '';
					if(text > 20) {
						text = '20+';
					}

					if(circle.length) {
						circle.html(text);
					} else {
						notifier.html(text);
					}
				}
			}
		}
	}
});
<commit_msg>added this.wiggling for scope call<commit_after>frappe.provide('frappe.desktop');

frappe.pages['desktop'].on_page_load = function(wrapper) {

	// load desktop
	if(!frappe.list_desktop) {
		frappe.desktop.set_background();
	}
	frappe.desktop.refresh(wrapper);
};

frappe.pages['desktop'].on_page_show = function(wrapper) {
	if(frappe.list_desktop) {
		$("body").attr("data-route", "list-desktop");
	}
};

$.extend(frappe.desktop, {
	refresh: function(wrapper) {
		if (wrapper) {
			this.wrapper = $(wrapper);
		}

		this.render();
		this.make_sortable();
	},

	render: function() {
		var me = this;
		frappe.utils.set_title(__("Desktop"));

		var template = frappe.list_desktop ? "desktop_list_view" : "desktop_icon_grid";

		var all_icons = frappe.get_desktop_icons();
		var explore_icon = {
			module_name: 'Explore',
			label: 'Explore',
			_label: __('Explore'),
			_id: 'Explore',
			_doctype: '',
			icon: 'octicon octicon-telescope',
			color: '#7578f6',
			link: 'modules'
		};
		explore_icon.app_icon = frappe.ui.app_icon.get_html(explore_icon);
		all_icons.push(explore_icon);

		frappe.desktop.wrapper.html(frappe.render_template(template, {
			// all visible icons
			desktop_items: all_icons,
		}));

		frappe.desktop.setup_module_click();

		// notifications
		frappe.desktop.show_pending_notifications();
		$(document).on("notification-update", function() {
			me.show_pending_notifications();
		});

		$(document).trigger("desktop-render");

	},

	render_help_messages: function(help_messages) {
		var wrapper = frappe.desktop.wrapper.find('.help-message-wrapper');
		var $help_messages = wrapper.find('.help-messages');

		var set_current_message = function(idx) {
			idx = cint(idx);
			wrapper.current_message_idx = idx;
			wrapper.find('.left-arrow, .right-arrow').addClass('disabled');
			wrapper.find('.help-message-item').addClass('hidden');
			wrapper.find('[data-message-idx="'+idx+'"]').removeClass('hidden');
			if(idx > 0) {
				wrapper.find('.left-arrow').removeClass('disabled');
			}
			if(idx < help_messages.length - 1) {
				wrapper.find('.right-arrow').removeClass('disabled');
			}
		}

		if(help_messages) {
			wrapper.removeClass('hidden');
			help_messages.forEach(function(message, i) {
				var $message = $('

')
					.attr('data-message-idx', i)
					.html(frappe.render_template('desktop_help_message', message))
					.appendTo($help_messages);

			});

			set_current_message(0);

			wrapper.find('.close').on('click', function() {
				wrapper.addClass('hidden');
			});
		}

		wrapper.find('.left-arrow').on('click', function() {
			if(wrapper.current_message_idx) {
				set_current_message(wrapper.current_message_idx - 1);
			}
		})

		wrapper.find('.right-arrow').on('click', function() {
			if(help_messages.length > wrapper.current_message_idx + 1) {
				set_current_message(wrapper.current_message_idx + 1);
			}
		});

	},

	setup_module_click: function() {
		this.wiggling = false; // wiggle, wiggle, wiggle.

		if(frappe.list_desktop) {
			frappe.desktop.wrapper.on("click", ".desktop-list-item", function() {
				frappe.desktop.open_module($(this));
			});
		} else {
			frappe.desktop.wrapper.on("click", ".app-icon", function() {
				if ( !this.wiggling ) {
					frappe.desktop.open_module($(this).parent());
				}
			});
		}
		frappe.desktop.wrapper.on("click", ".circle", function() {
			var doctype = $(this).attr('data-doctype');
			if(doctype) {
				frappe.ui.notifications.show_open_count_list(doctype);
			}
		});

		this.setup_wiggle()
	},

	setup_wiggle: () => {
		// Wiggle, Wiggle, Wiggle.
		const DURATION_LONG_PRESS = 1000;
		// lesser the antidode, more the wiggle (like your drunk uncle)
		// 75 seems good to replicate the iOS feels.
		const WIGGLE_ANTIDODE     = 75;

		var   timer_id      = 0;
		const $cases        = frappe.desktop.wrapper.find('.case-wrapper');
		const $icons        = frappe.desktop.wrapper.find('.app-icon');
		const $notis        = $(frappe.desktop.wrapper.find('.circle').toArray().filter((object) => {
			// This hack is so bad, I should punch myself.
			const doctype   = $(object).data('doctype');

			return doctype;
		}));

		const clearWiggle   = () => {
			const $closes   = $cases.find('.module-remove');
			$closes.hide();
			$notis.show();

			$icons.trigger('stopRumble');

			this.wiggling = false;
		};

		// initiate wiggling.
		$icons.jrumble({
			speed: WIGGLE_ANTIDODE // seems neat enough to match the iOS way
		});

		frappe.desktop.wrapper.on('mousedown', '.app-icon', () => {
			timer_id     = setTimeout(() => {
				this.wiggling = true;
				// hide all notifications.
				$notis.hide();

				$cases.each((i) => {
					const $case    = $($cases[i]);
					const template =
					`

									&times

					`;

					$case.append(template);
					const $close  = $case.find('.module-remove');
					const name    = $case.attr('title');
					$close.click(() => {
						// good enough to create dynamic dialogs?
						const dialog = new frappe.ui.Dialog({
							title: __(`Hide ${name}?`)
						});
						dialog.set_primary_action(__('Hide'), () => {
							frappe.call({
								method: 'frappe.desk.doctype.desktop_icon.desktop_icon.hide',
								args: { name: name },
								freeze: true,
								callback: (response) =>
								{
									if ( response.message ) {
										location.reload();
									}
								}
							})

							dialog.hide();

							clearWiggle();
						});
						// Hacks, Hacks and Hacks.
						var $cancel = dialog.get_close_btn();
						$cancel.click(() => {
							clearWiggle();
						});
						$cancel.html(__(`Cancel`));

						dialog.show();
					});
				});

				$icons.trigger('startRumble');
			}, DURATION_LONG_PRESS);
		});
		frappe.desktop.wrapper.on('mouseup mouseleave', '.app-icon', () => {
			clearTimeout(timer_id);
		});

		// also stop wiggling if clicked elsewhere.
		$('body').click((event) => {
			if ( this.wiggling ) {
				const $target = $(event.target);
				// our target shouldn't be .app-icons or .close
				const $parent = $target.parents('.case-wrapper');
				if ( $parent.length == 0 )
					clearWiggle();
			}
		});
		// end wiggle
	},

	open_module: function(parent) {
		var link = parent.attr("data-link");
		if(link) {
			if(link.indexOf('javascript:')===0) {
				eval(link.substr(11));
			} else if(link.substr(0, 1)==="/" || link.substr(0, 4)==="http") {
				window.open(link, "_blank");
			} else {
				frappe.set_route(link);
			}
			return false;
		} else {
			var module = frappe.get_module(parent.attr("data-name"));
			if (module && module.onclick) {
				module.onclick();
				return false;
			}
		}
	},

	make_sortable: function() {
		if (frappe.dom.is_touchscreen() || frappe.list_desktop) {
			return;
		}

		new Sortable($("#icon-grid").get(0), {
			onUpdate: function(event) {
				var new_order = [];
				$("#icon-grid .case-wrapper").each(function(i, e) {
					new_order.push($(this).attr("data-name"));
				});

				frappe.call({
					method: 'frappe.desk.doctype.desktop_icon.desktop_icon.set_order',
					args: {
						'new_order': new_order,
						'user': frappe.session.user
					},
					quiet: true
				});
			}
		});
	},

	set_background: function() {
		frappe.ui.set_user_background(frappe.boot.user.background_image, null,
			frappe.boot.user.background_style);
	},

	show_pending_notifications: function() {
		var modules_list = frappe.get_desktop_icons();
		for (var i=0, l=modules_list.length; i < l; i++) {
			var module = modules_list[i];

			var module_doctypes = frappe.boot.notification_info.module_doctypes[module.module_name];

			var sum = 0;
			if(module_doctypes) {
				if(frappe.boot.notification_info.open_count_doctype) {
					// sum all doctypes for a module
					for (var j=0, k=module_doctypes.length; j < k; j++) {
						var doctype = module_doctypes[j];
						sum += (frappe.boot.notification_info.open_count_doctype[doctype] || 0);
					}
				}
			} else if(frappe.boot.notification_info.open_count_doctype
				&& frappe.boot.notification_info.open_count_doctype[module.module_name]!=null) {
				// notification count explicitly for doctype
				sum = frappe.boot.notification_info.open_count_doctype[module.module_name];

			} else if(frappe.boot.notification_info.open_count_module
				&& frappe.boot.notification_info.open_count_module[module.module_name]!=null) {
				// notification count explicitly for module
				sum = frappe.boot.notification_info.open_count_module[module.module_name];
			}

			// if module found
			if(module._id.indexOf('/')===-1) {
				var notifier = $(".module-count-" + module._id);
				if(notifier.length) {
					notifier.toggle(sum ? true : false);
					var circle = notifier.find(".circle-text");
					var text = sum || '';
					if(text > 20) {
						text = '20+';
					}

					if(circle.length) {
						circle.html(text);
					} else {
						notifier.html(text);
					}
				}
			}
		}
	}
});
<|endoftext|>


<commit_before>// This module is neither officially-endorsed or supported by Wink
// however, Wink publishes an API and there are lots of wonderful open source tools that allow us to build on that API!

// jshint asi: true

var wink = { indicator: {} }

// data, e.g., datasources["WinKData"]["light_bulb"]["Kitchen"]

wink.indicator.value = function(data, property) {
    var value

    if (!data) return false
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return false
    if ({remotes: true, sprinklers: true }[data.object_type]) return false
    if (typeof property !== 'undefined') {
        if ((typeof data[property] === 'undefined') || (data[property] === null )) return true
        return { sensor_pods     : true
               , smoke_detectors : true
               , thermostats     : (property === 'temperature') || (property === 'humidity') || (property === 'mode')
	       , air_conditioners: (property === 'temperature')
               }[data.object_type] || (property === 'battery')
    }

    value = { buttons         : true
            , gangs           : true
            , garage_doors    : data.position > 0
            , hubs            : true
            , linked_services : true
            , light_bulbs     : data.powered
            , locks           : data.locked
            , sensor_pods     : true
            , shades          : data.position > 0
            , smoke_detectors : true
            , thermostats     : data.mode !== 'off'
            , air_conditioners: data.mode !== 'off'
            , unknown_devices: true
            , propane_tanks: true
            , piggy_banks: true
            , cameras: true
            }[data.object_type]
    return (typeof value !== 'undefined' ? value : data.powered)
}

wink.indicator.on_text = function(data, property) {
    return on_text(data, property)
}

var on_text = function(data, property) {
    var text, value

    if (!data) return ''
    text =  { buttons         : ''
	    , piggy_banks: 'Balance: ' + dollars(data.balance) + ' / Goal: ' + dollars(data.savings_goal)
            , garage_doors    : 'OPEN'
            , hubs            : (!data.update_needed ? 'OK' : 'UPDATE NEEDED')
            , linked_services : 'OK'
            , light_bulbs     : (data.brightness == 1.0  ? ''     : pct(data.brightness))
            , locks           : 'LOCKED'
            , sensor_pods     : 'PROPERTY?'
            , shades          : (data.position == 1.0    ? 'OPEN' : pct(data.position))
            , smoke_detectors : 'OK'
            , thermostats     : (data.cool_active ? 'COOL' : data.heat_active ? 'HEAT' : data.aux_active ? 'AUX' : data.fan_active ? 'FAN' : data.mode)
            , air_conditioners: (data.powered ? 'COOL to ' + dual_temp(data.max_set_point) : 'IDLE')
            , propane_tanks   : 'OK'
            }[data.object_type] || 'ON'
    if (typeof property === 'undefined') return text
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''
    value = data[property]
    text =  { co_detected     : (value ? 'CO DETECTED'     : '')
            , fault           : (value ? 'FAULT DETECTED'  : '')
            , liquid_detected : (value ? 'LEAK DETECTED'   : '')
            , locked          : (value ? 'LOCKED'          : 'UNLOCKED')
            , loudness        : (value ? 'LOUD'            : '')
            , noise           : (value ? 'NOISY'           : '')
            , motion          : (value ? 'MOTION'          : '')
            , opened          : (value ? 'OPEN'            : 'CLOSED')
            , presence        : (value ? 'PRESENCE'        : '')
            , smoke_detected  : (value ? 'SMOKE DETECTED'  : '')
            , tamper_detected : (value ? 'TAMPER DETECTED' : '')
            , vibration       : (value ? 'VIBRATION'       : '')

            , battery         : pct(value)
            , brightness      : pct(value)
            , co_severity     : pct(value)
            , humidity        : pct(value)
            , smoke_severity  : pct(value)

            , remaining       : pct(value)
            , temperature     : dual_temp(value)
//            , mode            : (value ==  'cool_only' ? 'Cool' : value == 'heat_only' ? 'Heat' : 'Auto')
	    , mode	      : tstat_mode(data,value)

            }[property]
    if (text === '') text = 'OK'

    return text
}

var pct = function(value) {
    return ((value > 1.0 ? value : value * 100).toFixed(0) + '%')
}
var dual_temp = function (value) {
    return (typeof value === 'number' ? (value.toFixed(1) + 'C / ' + ((value * 1.8) + 32).toFixed(1) + 'F') : '')
}

var dollars = function (value) {
    return (typeof value === 'number' ? (value / 100).toFixed(2) : '')
}

var tstat_mode = function (data, value) {
    //    return value
    return (value == 'cool_only' ? 'cool to: ' + dual_temp(data.min_set_point) : value == 'heat_only' ? 'heat to: ' + dual_temp(data.max_set_point) : 'auto: ' + dual_temp(data.min_set_point) + ' - ' + dual_temp(data.max_set_point))
}

wink.indicator.off_text = function(data, property) {
    return off_text(data, property)
}

var off_text = function(data, property) {
    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return 'ERR'
    if (typeof property !== 'undefined') return ''

    return { buttons          : 'IDLE'
           , garage_doors     : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           , locks            : 'UNLOCKED'
           , shades           : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           }[data.object_type] || 'OFF'
}

freeboard.addStyle('.wink-indicator-circle', "border-radius:50%;width:22px;height:22px;border:2px solid #3d3d3d;margin-top:5px;float:left;margin-right:10px;");
freeboard.addStyle('.wink-indicator-triangle', "border-radius:50%;width:0;height:0;border-left:15px solid transparent;border-right:15px solid transparent;margin-top:5px;float:left;margin-right:10px;");

wink.indicator.style_element = function(data, property) {
    var params = style_element(data, property)

    if (params === '') return params

    if (params.shape == 'triangle') {
        return ('

')
    }
    return ('

')
}

DynCol =function (hex, lum) {

	// validate hex string
	hex = String(hex).replace(/[^0-9a-f]/gi, '');
	if (hex.length < 6) {
		hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
	}
	lum = lum || 0;

	// convert to decimal and change luminosity
	var rgb = "#", c, i;
	for (i = 0; i < 3; i++) {
		c = parseInt(hex.substr(i*2,2), 16);
		c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
		rgb += ("00"+c).substr(c.length);
	}
	 console.log(lum);
	 console.log(rgb);
	return rgb;
}

var style_element = function(data, property) {
    var color, value
      , black  = '#000000'
      , blue   = '#00b8f1'
      , green  = '#00ff00'
      , red    = '#ff0000'
      , shape  = 'circle'
      , white  = '#ffffff'
      , yellow = '#eed202'

    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return { color: red, shape: 'triangle' }

    color = { binary_switches : (data.powered          ? white  : black)
	    , cameras	      : (data.capturing_video ? green : data.connection ? blue : black)
            , outlet          : (data.powered ? white : black)
            , buttons         : (!data.pressed         ? blue  : green)
            , garage_doors    : (data.position === 0.0 ? blue  : yellow)
            , hubs            : (!data.update_needed   ? blue  : yellow)
            , light_bulbs     : (data.powered          ? DynCol("#905030",data.brightness*5) : black)
            , locks           : (data.locked           ? blue  : yellow)
            , shades          : (data.position === 0.0 ? blue  : data.position === null ? yellow : green)
            , thermostats     : ({ cool_only: blue, heat_only: red }[data.mode] || green)
            , air_conditioners: (data.powered ? blue : green)
            , propane_tanks   : (data.remaining == 0 ? red : data.remaining == 1 ? blue : data.remaining > 0.66 ? green : data.remaining > 0.33 ? yellow : red)
            , piggy_banks     : '#'+data.nose_color
            }[data.object_type] || blue

    if (typeof property === 'undefined') return { color: color, shape: shape }
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''

    value = data[property]
    color = { co_detected     : value && red
            , fault           : value && red
            , liquid_detected : value && red
            , locked          : (!value) && red
            , loudness        : value && yellow
            , noise           : value && yellow
            , motion          : value && yellow
            , opened          : value && yellow
            , presence        : value && yellow
            , smoke_detected  : value && red
            , tamper_detected : value && red
            , vibration       : value && yellow

            , battery         : (value ==  1.0 ? blue : value > 0.66 ? green : value > 0.33 ? yellow : red)
            , brightness      : false
            , co_severity     : (value > 0) && red
            , humidity        : false
            , smoke_severity  : (value > 0) && red

            , temperature     : false
            }[property] || blue
    if ((color != blue) && (color != green)) shape = 'triangle'

    return { color: color, shape: shape }
}

<commit_msg>small change to accommodate away mode<commit_after>// This module is neither officially-endorsed or supported by Wink
// however, Wink publishes an API and there are lots of wonderful open source tools that allow us to build on that API!

// jshint asi: true

var wink = { indicator: {} }

// data, e.g., datasources["WinKData"]["light_bulb"]["Kitchen"]

wink.indicator.value = function(data, property) {
    var value

    if (!data) return false
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return false
    if ({remotes: true, sprinklers: true }[data.object_type]) return false
    if (typeof property !== 'undefined') {
        if ((typeof data[property] === 'undefined') || (data[property] === null )) return true
        return { sensor_pods     : true
               , smoke_detectors : true
               , thermostats     : (property === 'temperature') || (property === 'humidity') || (property === 'mode')
	       , air_conditioners: (property === 'temperature')
               }[data.object_type] || (property === 'battery')
    }

    value = { buttons         : true
            , gangs           : true
            , garage_doors    : data.position > 0
            , hubs            : true
            , linked_services : true
            , light_bulbs     : data.powered
            , locks           : data.locked
            , sensor_pods     : true
            , shades          : data.position > 0
            , smoke_detectors : true
            , thermostats     : data.mode !== 'off'
            , air_conditioners: data.mode !== 'off'
            , unknown_devices: true
            , propane_tanks: true
            , piggy_banks: true
            , cameras: true
            }[data.object_type]
    return (typeof value !== 'undefined' ? value : data.powered)
}

wink.indicator.on_text = function(data, property) {
    return on_text(data, property)
}

var on_text = function(data, property) {
    var text, value

    if (!data) return ''
    text =  { buttons         : ''
	    , piggy_banks: 'Balance: ' + dollars(data.balance) + ' / Goal: ' + dollars(data.savings_goal)
            , garage_doors    : 'OPEN'
            , hubs            : (!data.update_needed ? 'OK' : 'UPDATE NEEDED')
            , linked_services : 'OK'
            , light_bulbs     : (data.brightness == 1.0  ? ''     : pct(data.brightness))
            , locks           : 'LOCKED'
            , sensor_pods     : 'PROPERTY?'
            , shades          : (data.position == 1.0    ? 'OPEN' : pct(data.position))
            , smoke_detectors : 'OK'
            , thermostats     : (data.cool_active ? 'COOL' : data.heat_active ? 'HEAT' : data.aux_active ? 'AUX' : data.fan_active ? 'FAN' : data.mode)
            , air_conditioners: (data.powered ? 'COOL to ' + dual_temp(data.max_set_point) : 'IDLE')
            , propane_tanks   : 'OK'
            }[data.object_type] || 'ON'
    if (typeof property === 'undefined') return text
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''
    value = data[property]
    text =  { co_detected     : (value ? 'CO DETECTED'     : '')
            , fault           : (value ? 'FAULT DETECTED'  : '')
            , liquid_detected : (value ? 'LEAK DETECTED'   : '')
            , locked          : (value ? 'LOCKED'          : 'UNLOCKED')
            , loudness        : (value ? 'LOUD'            : '')
            , noise           : (value ? 'NOISY'           : '')
            , motion          : (value ? 'MOTION'          : '')
            , opened          : (value ? 'OPEN'            : 'CLOSED')
            , presence        : (value ? 'PRESENCE'        : '')
            , smoke_detected  : (value ? 'SMOKE DETECTED'  : '')
            , tamper_detected : (value ? 'TAMPER DETECTED' : '')
            , vibration       : (value ? 'VIBRATION'       : '')

            , battery         : pct(value)
            , brightness      : pct(value)
            , co_severity     : pct(value)
            , humidity        : pct(value)
            , smoke_severity  : pct(value)

            , remaining       : pct(value)
            , temperature     : dual_temp(value)
//            , mode            : (value ==  'cool_only' ? 'Cool' : value == 'heat_only' ? 'Heat' : 'Auto')
	    , mode	      : tstat_mode(data,value)

            }[property]
    if (text === '') text = 'OK'

    return text
}

var pct = function(value) {
    return ((value > 1.0 ? value : value * 100).toFixed(0) + '%')
}
var dual_temp = function (value) {
    return (typeof value === 'number' ? (value.toFixed(1) + 'C / ' + ((value * 1.8) + 32).toFixed(1) + 'F') : '')
}

var dollars = function (value) {
    return (typeof value === 'number' ? (value / 100).toFixed(2) : '')
}

var tstat_mode = function (data, value) {
    //    return value
    if (data.users_away) return 'Away mode'
    return (value == 'cool_only' ? 'cool to: ' + dual_temp(data.min_set_point) : value == 'heat_only' ? 'heat to: ' + dual_temp(data.max_set_point) : 'auto: ' + dual_temp(data.min_set_point) + ' - ' + dual_temp(data.max_set_point))
}

wink.indicator.off_text = function(data, property) {
    return off_text(data, property)
}

var off_text = function(data, property) {
    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return 'ERR'
    if (typeof property !== 'undefined') return ''

    return { buttons          : 'IDLE'
           , garage_doors     : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           , locks            : 'UNLOCKED'
           , shades           : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           }[data.object_type] || 'OFF'
}

freeboard.addStyle('.wink-indicator-circle', "border-radius:50%;width:22px;height:22px;border:2px solid #3d3d3d;margin-top:5px;float:left;margin-right:10px;");
freeboard.addStyle('.wink-indicator-triangle', "border-radius:50%;width:0;height:0;border-left:15px solid transparent;border-right:15px solid transparent;margin-top:5px;float:left;margin-right:10px;");

wink.indicator.style_element = function(data, property) {
    var params = style_element(data, property)

    if (params === '') return params

    if (params.shape == 'triangle') {
        return ('

')
    }
    return ('

')
}

DynCol =function (hex, lum) {

	// validate hex string
	hex = String(hex).replace(/[^0-9a-f]/gi, '');
	if (hex.length < 6) {
		hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
	}
	lum = lum || 0;

	// convert to decimal and change luminosity
	var rgb = "#", c, i;
	for (i = 0; i < 3; i++) {
		c = parseInt(hex.substr(i*2,2), 16);
		c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
		rgb += ("00"+c).substr(c.length);
	}
	 console.log(lum);
	 console.log(rgb);
	return rgb;
}

var style_element = function(data, property) {
    var color, value
      , black  = '#000000'
      , blue   = '#00b8f1'
      , green  = '#00ff00'
      , red    = '#ff0000'
      , shape  = 'circle'
      , white  = '#ffffff'
      , yellow = '#eed202'

    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return { color: red, shape: 'triangle' }

    color = { binary_switches : (data.powered          ? white  : black)
	    , cameras	      : (data.capturing_video ? green : data.connection ? blue : black)
            , outlet          : (data.powered ? white : black)
            , buttons         : (!data.pressed         ? blue  : green)
            , garage_doors    : (data.position === 0.0 ? blue  : yellow)
            , hubs            : (!data.update_needed   ? blue  : yellow)
            , light_bulbs     : (data.powered          ? DynCol("#905030",data.brightness*5) : black)
            , locks           : (data.locked           ? blue  : yellow)
            , shades          : (data.position === 0.0 ? blue  : data.position === null ? yellow : green)
            , thermostats     : ({ cool_only: blue, heat_only: red }[data.mode] || green)
            , air_conditioners: (data.powered ? blue : green)
            , propane_tanks   : (data.remaining == 0 ? red : data.remaining == 1 ? blue : data.remaining > 0.66 ? green : data.remaining > 0.33 ? yellow : red)
            , piggy_banks     : '#'+data.nose_color
            }[data.object_type] || blue

    if (typeof property === 'undefined') return { color: color, shape: shape }
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''

    value = data[property]
    color = { co_detected     : value && red
            , fault           : value && red
            , liquid_detected : value && red
            , locked          : (!value) && red
            , loudness        : value && yellow
            , noise           : value && yellow
            , motion          : value && yellow
            , opened          : value && yellow
            , presence        : value && yellow
            , smoke_detected  : value && red
            , tamper_detected : value && red
            , vibration       : value && yellow

            , battery         : (value ==  1.0 ? blue : value > 0.66 ? green : value > 0.33 ? yellow : red)
            , brightness      : false
            , co_severity     : (value > 0) && red
            , humidity        : false
            , smoke_severity  : (value > 0) && red

            , temperature     : false
            }[property] || blue
    if ((color != blue) && (color != green)) shape = 'triangle'

    return { color: color, shape: shape }
}

<|endoftext|>


<commit_before>var options;
chrome.extension.sendRequest({}, function(response) {
	setAllTheSettings(response);
});

function setAllTheSettings(response) {
	options = response;
	bodyClasses = document.body.classList;
	document.body.addEventListener("DOMNodeInserted", eventDispatcher);
	if(options.circled_avatars == "true") {
		bodyClasses.add("btd-circle-avatars");
	}
	if(options.fading_pictures == "true") {
		bodyClasses.add("btd-profile-pictures-fading");
	}
	if(options.reduced_padding == "true") {
		bodyClasses.add("btd-reduced-padding");
	}
	if(options.no_columns_icons == "true") {
		bodyClasses.add("btd-no-columns-icons");
	}
}

function eventDispatcher() {
	// If the event.target is the text (TweetDeck updates timestamp at regular intervals) then we can get the .txt-mute element and tweak it in real-time
	if(event.relatedNode.className.indexOf("txt-mute") != -1 && options.timestamp != "relative") {
		timeIsNotRelative(event.relatedNode, options.timestamp);
	}
	// If it's not a .txt-mute element, it must be a tweet or something similar, let's check it !
	else if(event.target.className && event.target.className.indexOf("stream-item") != -1) {

		// Applying the timestamp
		if(options.timestamp != "relative") {
			if(event.target.querySelector("time")){
				timeIsNotRelative(event.target.querySelector("time > *"), options.timestamp);
			}
		}

		// Tweaking the name displaying
		nameDisplay(event.target.querySelectorAll("a[rel='user']:not(.item-img)"), options.name_display);

		// If asked, removing t.co links
		if(options.url_redirection == "true"){
			useFullUrl(event.target);
		}

		// If asked, creating the non-pic.twitter image previews
		if(options.img_preview != "false"){
			links = event.target.querySelectorAll("p > a[data-full-url]");
			if(links.length > 0) {
			linkUrl = links[0].getAttribute("data-full-url");
				if(linkUrl.indexOf("imgur.com") != -1 && linkUrl.indexOf("/a/") == -1 && linkUrl.indexOf("/gallery/") == -1){
					createPreviewDiv(links[0],"imgur");
				} else if(linkUrl.indexOf("d.pr/i") != -1) {
					createPreviewDiv(links[0],"droplr");
				}
			}
		}
	} else if(event.relatedNode.classList.contains("typeahead")) {
		if(options.typeahead_display_username_only == "true") {
			for (var i = event.relatedNode.querySelectorAll("strong.fullname").length - 1; i >= 0; i--) {
				event.relatedNode.querySelectorAll("strong.fullname")[i].remove();
			};
			for (var i = event.relatedNode.querySelectorAll("span.username").length - 1; i >= 0; i--) {
				event.relatedNode.querySelectorAll("span.username")[i].style.fontWeight= "bold";
				event.relatedNode.querySelectorAll("span.username")[i].classList.add("fullname");
				event.relatedNode.querySelectorAll("span.username")[i].classList.remove("username");
			};
		}
	}
}

function createPreviewDiv(element, provider) {
	// Getting the full URL for later
	linkURL = element.getAttribute("data-full-url");
	thumbSize = options.img_preview;

	// Creating the elements, replicating the same layout as TweetDeck's one
	previewDiv = document.createElement("div");
	previewDiv.className = "js-media media-preview position-rel btd-preview "+provider;
	previewDivChild = document.createElement("div");
	previewDivChild.className = "js-media-preview-container position-rel margin-vm";
	previewLink = document.createElement("a");
	previewLink.className = "js-media-image-link block med-link media-item media-size-"+thumbSize+"";
	// Little difference, using rel=url otherwhise TweetDeck will treat it as a "real" media preview, therefore "blocking" the click on it
	previewLink.setAttribute("rel","url");
	previewLink.href = linkURL;
	previewLink.setAttribute("target","_blank");

	if(provider == "imgur") {
		// Imgur supported extensions (from http://imgur.com/faq#types)
		extensions = ["jpg","jpeg","gif","png","apng"];

		if(thumbSize == "small") suffix = "t";
		if(thumbSize == "medium") suffix = "m";
		if(thumbSize == "large") suffix = "l";

		var regex = new RegExp(extensions.join('|'));
		var thumbnailUrl;

		// If link has an extension, replacing it by .jpg (png would be possible, but we don't want to have big previews)
		if (regex.test(previewLink.href)) {
			for (var i = extensions.length - 1; i >= 0; i--) {
				thumbnailUrl = linkURL.replace("."+extensions[i],suffix+".jpg");
			};
		} else {
			// Even if it doesn't have any extension, putting it one at the end so we can get something
			thumbnailUrl = linkURL+suffix+".jpg";
		}

		// Replacing imgur.com by i.imgur.com and http by https
		thumbnailUrl = thumbnailUrl.replace("/imgur.com","/i.imgur.com").replace("http","https");

	} else if(provider == "droplr") {

		// Depending of the thumbSize option we're getting d.pr/i/1234/small or d.pr/i/1234/medium (it seems like Droplr hasn't a "large" option)
		if(thumbSize == "small") {
			suffix = thumbSize;
		} else {
			suffix = "medium";
		}
		// Removing the last "/" if present and adding one+suffix
		thumbnailUrl = linkURL.replace(/\/$/,"");
		thumbnailUrl = thumbnailUrl+"/"+suffix;
	}

	// Applying our thumbnail as a background-image of the preview div
	previewLink.style.backgroundImage = "url("+thumbnailUrl+")";

	// Constructing our final div
	previewDivChild.appendChild(previewLink);
	previewDiv.appendChild(previewDivChild);

	// Adding it next to the <p> element, just before <footer> in a tweet
	pElement = element.parentNode.parentNode.querySelector("p.js-tweet-text");
	if(pElement.nextElementSibling) {
		pElement.parentNode.insertBefore(previewDiv, pElement.nextElementSibling);
	}
}

function timeIsNotRelative(element, mode) {
	// Getting the timestamp of an item
	d = element.parentNode.getAttribute("data-time");
	// Creating a Date object with it
	td = new Date(parseInt(d));
	if(options.full_after_24h == "true") {
		now = new Date();
		difference = now - td;
	    var msPerMinute = 60 * 1000;
	    var msPerHour = msPerMinute * 60;
	    var msPerDay = msPerHour * 24;
	}

	// Creating year/day/month/minutes/hours variables and applying the lead zeros if necessary
	year = td.getFullYear();
	if(year < 10) year = "0"+year;
	month = td.getMonth()+1;
	if(month < 10) month = "0"+month;
	minutes = td.getMinutes();
	if(minutes < 10) minutes = "0"+minutes;
	hours = td.getHours();
	if(hours < 10) hours = "0"+hours;
	day = td.getDate();
	if(day < 10) day = "0"+day;

	// Handling "US" date format
	if(options.full_after_24h == "true" && difference < msPerDay) {
		dateString = hours+":"+minutes;
	} else {
		if(mode == "absolute_us"){
			dateString =  month+"/"+day+"/"+year+" "+hours+":"+minutes;
		} else {
			dateString =  day+"/"+month+"/"+year+" "+hours+":"+minutes;
		}
	}
	// Changing the content of the "time > a" element with the absolute time
	element.innerHTML = dateString;
	element.classList.add("txt-mute");
}

function nameDisplay(elements, mode) {
	if(mode == "username"){
		// If we just want the @username.
		for (var i = elements.length - 1; i >= 0; i--) {
			// If the username is NOT in a tweet (typically in a <p> element), do the thing
			if(elements[i].parentNode.tagName != "P") {
				// Removing "http://twitter.com" and the last "/" in the link to get the @username
				username = elements[i].getAttribute("href").replace(/http(|s):\/\/twitter.com\//,"").replace(/\//g,"");

				// Placing the username in b.fullname if found or in span.username
				if(elements[i].querySelector("b.fullname")){
					elements[i].querySelector("b.fullname").innerHTML = username;
				} else {
					elements[i].innerHTML = username;
				}
				if(elements[i].querySelector("span.username")){
					elements[i].querySelector("span.username").remove();
				}
			}
		};
	} else if(mode == "fullname") {
		// If we just want the fullname, basically we exterminate the usernames
		for (var i = document.querySelectorAll(".username").length - 1; i >= 0; i--) {
		// Ignoring the elements argument because I'm lazy and it works so, hey ?
		document.querySelectorAll(".username")[i].remove();
		};
	} else if(mode == "inverted") {
		// If we want the @username AND the full name, we'll have to swap them
		for (var i = elements.length - 1; i >= 0; i--) {
			// If the username is NOT in a tweet (typically in a <p> element), do the thing
			if(elements[i].parentNode.tagName != "P") {
				// Removing "http://twitter.com" and the last "/" in the link to get the @username
				username = elements[i].getAttribute("href").replace(/http(|s):\/\/twitter.com\//,"").replace(/\//g,"");
				if(elements[i].querySelector("b.fullname")) {
					fullname = elements[i].querySelector("b.fullname").innerHTML;
				}

				// Placing the username in b.fullname if found or in span.username
				if(elements[i].querySelector("b.fullname")) {
					elements[i].querySelector("b.fullname").innerHTML = username;
				} else {
					elements[i].innerHTML = username;
				}

				if(elements[i].querySelector("span.username")) {
					elements[i].querySelector("span.username span.at").remove();
					elements[i].querySelector("span.username").innerHTML = fullname;
				}

			}
		};
	}
}

function useFullUrl(element) {
	// Pretty easy, getting the data-full-url content and applying it as href in the links. Bye bye t.co !
	links = element.querySelectorAll("a[data-full-url]");
	for (var i = links.length - 1; i >= 0; i--) {
		fullLink = links[i].getAttribute("data-full-url");
		links[i].href = fullLink;
	};
}
<commit_msg>Removed unused code<commit_after>var options;
chrome.extension.sendRequest({}, function(response) {
	setAllTheSettings(response);
});

function setAllTheSettings(response) {
	options = response;
	bodyClasses = document.body.classList;
	document.body.addEventListener("DOMNodeInserted", eventDispatcher);
	if(options.circled_avatars == "true") {
		bodyClasses.add("btd-circle-avatars");
	}
	if(options.reduced_padding == "true") {
		bodyClasses.add("btd-reduced-padding");
	}
	if(options.no_columns_icons == "true") {
		bodyClasses.add("btd-no-columns-icons");
	}
}

function eventDispatcher() {
	// If the event.target is the text (TweetDeck updates timestamp at regular intervals) then we can get the .txt-mute element and tweak it in real-time
	if(event.relatedNode.className.indexOf("txt-mute") != -1 && options.timestamp != "relative") {
		timeIsNotRelative(event.relatedNode, options.timestamp);
	}
	// If it's not a .txt-mute element, it must be a tweet or something similar, let's check it !
	else if(event.target.className && event.target.className.indexOf("stream-item") != -1) {

		// Applying the timestamp
		if(options.timestamp != "relative") {
			if(event.target.querySelector("time")){
				timeIsNotRelative(event.target.querySelector("time > *"), options.timestamp);
			}
		}

		// Tweaking the name displaying
		nameDisplay(event.target.querySelectorAll("a[rel='user']:not(.item-img)"), options.name_display);

		// If asked, removing t.co links
		if(options.url_redirection == "true"){
			useFullUrl(event.target);
		}

		// If asked, creating the non-pic.twitter image previews
		if(options.img_preview != "false"){
			links = event.target.querySelectorAll("p > a[data-full-url]");
			if(links.length > 0) {
			linkUrl = links[0].getAttribute("data-full-url");
				if(linkUrl.indexOf("imgur.com") != -1 && linkUrl.indexOf("/a/") == -1 && linkUrl.indexOf("/gallery/") == -1){
					createPreviewDiv(links[0],"imgur");
				} else if(linkUrl.indexOf("d.pr/i") != -1) {
					createPreviewDiv(links[0],"droplr");
				}
			}
		}
	} else if(event.relatedNode.classList.contains("typeahead")) {
		if(options.typeahead_display_username_only == "true") {
			for (var i = event.relatedNode.querySelectorAll("strong.fullname").length - 1; i >= 0; i--) {
				event.relatedNode.querySelectorAll("strong.fullname")[i].remove();
			};
			for (var i = event.relatedNode.querySelectorAll("span.username").length - 1; i >= 0; i--) {
				event.relatedNode.querySelectorAll("span.username")[i].style.fontWeight= "bold";
				event.relatedNode.querySelectorAll("span.username")[i].classList.add("fullname");
				event.relatedNode.querySelectorAll("span.username")[i].classList.remove("username");
			};
		}
	}
}

function createPreviewDiv(element, provider) {
	// Getting the full URL for later
	linkURL = element.getAttribute("data-full-url");
	thumbSize = options.img_preview;

	// Creating the elements, replicating the same layout as TweetDeck's one
	previewDiv = document.createElement("div");
	previewDiv.className = "js-media media-preview position-rel btd-preview "+provider;
	previewDivChild = document.createElement("div");
	previewDivChild.className = "js-media-preview-container position-rel margin-vm";
	previewLink = document.createElement("a");
	previewLink.className = "js-media-image-link block med-link media-item media-size-"+thumbSize+"";
	// Little difference, using rel=url otherwhise TweetDeck will treat it as a "real" media preview, therefore "blocking" the click on it
	previewLink.setAttribute("rel","url");
	previewLink.href = linkURL;
	previewLink.setAttribute("target","_blank");

	if(provider == "imgur") {
		// Imgur supported extensions (from http://imgur.com/faq#types)
		extensions = ["jpg","jpeg","gif","png","apng"];

		if(thumbSize == "small") suffix = "t";
		if(thumbSize == "medium") suffix = "m";
		if(thumbSize == "large") suffix = "l";

		var regex = new RegExp(extensions.join('|'));
		var thumbnailUrl;

		// If link has an extension, replacing it by .jpg (png would be possible, but we don't want to have big previews)
		if (regex.test(previewLink.href)) {
			for (var i = extensions.length - 1; i >= 0; i--) {
				thumbnailUrl = linkURL.replace("."+extensions[i],suffix+".jpg");
			};
		} else {
			// Even if it doesn't have any extension, putting it one at the end so we can get something
			thumbnailUrl = linkURL+suffix+".jpg";
		}

		// Replacing imgur.com by i.imgur.com and http by https
		thumbnailUrl = thumbnailUrl.replace("/imgur.com","/i.imgur.com").replace("http","https");

	} else if(provider == "droplr") {

		// Depending of the thumbSize option we're getting d.pr/i/1234/small or d.pr/i/1234/medium (it seems like Droplr hasn't a "large" option)
		if(thumbSize == "small") {
			suffix = thumbSize;
		} else {
			suffix = "medium";
		}
		// Removing the last "/" if present and adding one+suffix
		thumbnailUrl = linkURL.replace(/\/$/,"");
		thumbnailUrl = thumbnailUrl+"/"+suffix;
	}

	// Applying our thumbnail as a background-image of the preview div
	previewLink.style.backgroundImage = "url("+thumbnailUrl+")";

	// Constructing our final div
	previewDivChild.appendChild(previewLink);
	previewDiv.appendChild(previewDivChild);

	// Adding it next to the <p> element, just before <footer> in a tweet
	pElement = element.parentNode.parentNode.querySelector("p.js-tweet-text");
	if(pElement.nextElementSibling) {
		pElement.parentNode.insertBefore(previewDiv, pElement.nextElementSibling);
	}
}

function timeIsNotRelative(element, mode) {
	// Getting the timestamp of an item
	d = element.parentNode.getAttribute("data-time");
	// Creating a Date object with it
	td = new Date(parseInt(d));
	if(options.full_after_24h == "true") {
		now = new Date();
		difference = now - td;
	    var msPerMinute = 60 * 1000;
	    var msPerHour = msPerMinute * 60;
	    var msPerDay = msPerHour * 24;
	}

	// Creating year/day/month/minutes/hours variables and applying the lead zeros if necessary
	year = td.getFullYear();
	if(year < 10) year = "0"+year;
	month = td.getMonth()+1;
	if(month < 10) month = "0"+month;
	minutes = td.getMinutes();
	if(minutes < 10) minutes = "0"+minutes;
	hours = td.getHours();
	if(hours < 10) hours = "0"+hours;
	day = td.getDate();
	if(day < 10) day = "0"+day;

	// Handling "US" date format
	if(options.full_after_24h == "true" && difference < msPerDay) {
		dateString = hours+":"+minutes;
	} else {
		if(mode == "absolute_us"){
			dateString =  month+"/"+day+"/"+year+" "+hours+":"+minutes;
		} else {
			dateString =  day+"/"+month+"/"+year+" "+hours+":"+minutes;
		}
	}
	// Changing the content of the "time > a" element with the absolute time
	element.innerHTML = dateString;
	element.classList.add("txt-mute");
}

function nameDisplay(elements, mode) {
	if(mode == "username"){
		// If we just want the @username.
		for (var i = elements.length - 1; i >= 0; i--) {
			// If the username is NOT in a tweet (typically in a <p> element), do the thing
			if(elements[i].parentNode.tagName != "P") {
				// Removing "http://twitter.com" and the last "/" in the link to get the @username
				username = elements[i].getAttribute("href").replace(/http(|s):\/\/twitter.com\//,"").replace(/\//g,"");

				// Placing the username in b.fullname if found or in span.username
				if(elements[i].querySelector("b.fullname")){
					elements[i].querySelector("b.fullname").innerHTML = username;
				} else {
					elements[i].innerHTML = username;
				}
				if(elements[i].querySelector("span.username")){
					elements[i].querySelector("span.username").remove();
				}
			}
		};
	} else if(mode == "fullname") {
		// If we just want the fullname, basically we exterminate the usernames
		for (var i = document.querySelectorAll(".username").length - 1; i >= 0; i--) {
		// Ignoring the elements argument because I'm lazy and it works so, hey ?
		document.querySelectorAll(".username")[i].remove();
		};
	} else if(mode == "inverted") {
		// If we want the @username AND the full name, we'll have to swap them
		for (var i = elements.length - 1; i >= 0; i--) {
			// If the username is NOT in a tweet (typically in a <p> element), do the thing
			if(elements[i].parentNode.tagName != "P") {
				// Removing "http://twitter.com" and the last "/" in the link to get the @username
				username = elements[i].getAttribute("href").replace(/http(|s):\/\/twitter.com\//,"").replace(/\//g,"");
				if(elements[i].querySelector("b.fullname")) {
					fullname = elements[i].querySelector("b.fullname").innerHTML;
				}

				// Placing the username in b.fullname if found or in span.username
				if(elements[i].querySelector("b.fullname")) {
					elements[i].querySelector("b.fullname").innerHTML = username;
				} else {
					elements[i].innerHTML = username;
				}

				if(elements[i].querySelector("span.username")) {
					elements[i].querySelector("span.username span.at").remove();
					elements[i].querySelector("span.username").innerHTML = fullname;
				}

			}
		};
	}
}

function useFullUrl(element) {
	// Pretty easy, getting the data-full-url content and applying it as href in the links. Bye bye t.co !
	links = element.querySelectorAll("a[data-full-url]");
	for (var i = links.length - 1; i >= 0; i--) {
		fullLink = links[i].getAttribute("data-full-url");
		links[i].href = fullLink;
	};
}
<|endoftext|>


<commit_before>;
var scripts = scripts || {};

jQuery.validator.addMethod("lettersonly", function(value, element) {
	return this.optional(element) || /^[a-z," "]+$/i.test(value);
}, "Letters only please");

scripts.Common = {
	detecting: function () {
		$('html').removeClass('no-js');
	},

	isModernBrowser: function () {
		if ( // modernizer alternative
			'querySelector' in document &&
			'localStorage' in window &&
			'addEventListener' in window
			) {
			return true;
		} else {
			return false;
		}
	},

	jsPlaceholderInit: function () {
		$('input[placeholder], textarea[placeholder]').placeholder();
	},

	jqueryValidateInit: function () {
		$('#form-decloration').validate({
			errorClass: "invalid",
			wrapper: "div class='form__msg'",
			errorElement: "p",
			errorPlacement: function(error, element) {
				error.insertBefore(element);
			},
			submitHandler: function(form) {
				form.submit();
			}
		});

		$(".js-validate-digits").rules( "add", {
			required: true,
			minlength: 2,
			digits: true,
			messages: {
				required: "Required input",
				minlength: jQuery.format("Please, at least {0} characters are necessary"),
				digits: jQuery.format("Please, at least {0} digits are necessary")
			}
		});

		$(".js-validate-letters").rules( "add", {
			required: true,
			minlength: 2,
			digits: false,
			messages: {
				required: "Required input",
				minlength: jQuery.format("Please, at least {0} digits are necessary"),
				digits: jQuery.format("Please, at least {0} digits are necessary")
			}
		});
	},

	toggleFormSection: function() {
		var ctrlInput = $(".js-related-control").find("input[type='checkbox']");

		ctrlInput.each(function() {
			var self = $(this),
				ctrlInputInit = self.is(":checked"),
				sectionBlc = self.parents(".js-related-control").next(".js-related-section");

			sectionBlc[ctrlInputInit ? "removeClass" : "addClass"]("i-inputs_disabled");
			sectionBlc.find("input, select, textarea").attr("disabled", !ctrlInputInit);
		});

		ctrlInput.on('click', function() {
			var sectionBlc = $(this).parents(".js-related-control").next(".js-related-section");

			sectionBlc[this.checked ? "removeClass" : "addClass"]("i-inputs_disabled");
			sectionBlc.find("input, select, textarea").attr("disabled", !this.checked);
		});
	},

	inputActions: function() {
		var inputs = $('input[data-inp-act], select[data-inp-act]');

		inputs.wrap("");
		$('').insertAfter(inputs);

		inputs.each(function() {
			var input = $(this),
				inputId = input.attr('id'),
				inputActions = input.data('inp-act').split(',');

			$.each(inputActions, function (key, value) {
				$(input.next('.form__input-w-ico__ico'))
					.append('' +
				'<input type="checkbox" name="'+ inputId + '_' + value +'" tabindex="-1" />');
			});
		});

		$(".form__input-act input[type='checkbox']").on('click', function() {
			$(this).parent().toggleClass('js-ico-checked');
		});
	},

	cloneyaInit: function () {
		$('.js-clone-wrapper').cloneya({
			limit: 10,
			cloneThis: '.js-toclone',
			valueClone: false,
			dataClone: false,
			deepClone: false,
			cloneButton: '.js-clone',
			deleteButton: '.js-clone-delete',
			clonePosition: 'after',
			serializeID: true,
			ignore: 'label.error',
			defaultRender: false
		});

		//$('.js-clone').on('click', function(e) {
		//	e.preventDefault();
		//	$('.js-clone-wrapper').trigger('clone_clone');
		//});
		//
		//$('.js-clone-delete').on('click', function(e) {
		//	e.preventDefault();
		//	$('.js-clone-wrapper').trigger('clone_delete');
		//});
	},

	init: function () {
		var scripts = this;

		scripts.detecting();

		$(function () {
//			if (!scripts.isModernBrowser) {
//
//			}

			//scripts.jqueryValidateInit();
			scripts.toggleFormSection();
			scripts.inputActions();
			scripts.cloneyaInit();
		});

		return scripts;
	}
};

scripts.Common.init();
<commit_msg>CHANGED: toggel form improved<commit_after>;
var scripts = scripts || {};

jQuery.validator.addMethod("lettersonly", function(value, element) {
	return this.optional(element) || /^[a-z," "]+$/i.test(value);
}, "Letters only please");

scripts.Common = {
	detecting: function () {
		$('html').removeClass('no-js');
	},

	isModernBrowser: function () {
		if ( // modernizer alternative
			'querySelector' in document &&
			'localStorage' in window &&
			'addEventListener' in window
			) {
			return true;
		} else {
			return false;
		}
	},

	jsPlaceholderInit: function () {
		$('input[placeholder], textarea[placeholder]').placeholder();
	},

	jqueryValidateInit: function () {
		$('#form-decloration').validate({
			errorClass: "invalid",
			wrapper: "div class='form__msg'",
			errorElement: "p",
			errorPlacement: function(error, element) {
				error.insertBefore(element);
			},
			submitHandler: function(form) {
				form.submit();
			}
		});

		$(".js-validate-digits").rules( "add", {
			required: true,
			minlength: 2,
			digits: true,
			messages: {
				required: "Required input",
				minlength: jQuery.format("Please, at least {0} characters are necessary"),
				digits: jQuery.format("Please, at least {0} digits are necessary")
			}
		});

		$(".js-validate-letters").rules( "add", {
			required: true,
			minlength: 2,
			digits: false,
			messages: {
				required: "Required input",
				minlength: jQuery.format("Please, at least {0} digits are necessary"),
				digits: jQuery.format("Please, at least {0} digits are necessary")
			}
		});
	},

	toggleFormSection: function() {
		var ctrlInput = $(".js-related-control").find("input[type='checkbox']");

		function disableOnChecked (el, checked) {
			var sectionBlc = el.parents(".js-related-control").next(".js-related-section");

			sectionBlc[checked ? "removeClass" : "addClass"]("i-inputs_disabled");
			sectionBlc.find("input, select, textarea").attr("disabled", !checked);
		}

		ctrlInput.each(function() {
			var self = $(this),
				ctrlInputInit = self.is(":checked");

			disableOnChecked(self, ctrlInputInit);
		});

		ctrlInput.on('click', function() {
			disableOnChecked($(this), this.checked);
		});
	},

	inputActions: function() {
		var inputs = $('input[data-inp-act], select[data-inp-act]');

		inputs.wrap("");
		$('').insertAfter(inputs);

		inputs.each(function() {
			var input = $(this),
				inputId = input.attr('id'),
				inputActions = input.data('inp-act').split(',');

			$.each(inputActions, function (key, value) {
				$(input.next('.form__input-w-ico__ico'))
					.append('' +
				'<input type="checkbox" name="'+ inputId + '_' + value +'" tabindex="-1" />');
			});
		});

		$(".form__input-act input[type='checkbox']").on('click', function() {
			$(this).parent().toggleClass('js-ico-checked');
		});
	},

	cloneyaInit: function () {
		$('.js-clone-wrapper').cloneya({
			limit: 10,
			cloneThis: '.js-toclone',
			valueClone: false,
			dataClone: false,
			deepClone: false,
			cloneButton: '.js-clone',
			deleteButton: '.js-clone-delete',
			clonePosition: 'after',
			serializeID: true,
			ignore: 'label.error',
			defaultRender: false
		});

		//$('.js-clone').on('click', function(e) {
		//	e.preventDefault();
		//	$('.js-clone-wrapper').trigger('clone_clone');
		//});
		//
		//$('.js-clone-delete').on('click', function(e) {
		//	e.preventDefault();
		//	$('.js-clone-wrapper').trigger('clone_delete');
		//});
	},

	init: function () {
		var scripts = this;

		scripts.detecting();

		$(function () {
//			if (!scripts.isModernBrowser) {
//
//			}

			//scripts.jqueryValidateInit();
			scripts.toggleFormSection();
			scripts.inputActions();
			scripts.cloneyaInit();
		});

		return scripts;
	}
};

scripts.Common.init();
<|endoftext|>


<commit_before>/* ===================================================
 * JavaScript 2ch-client Library
 * https://github.com/sh19910711/js2ch
 * ===================================================
 * Copyright (c) 2013 Hiroyuki Sano
 *
 * Licensed under MIT License.
 * http://opensource.org/licenses/MIT
 * =================================================== */
/**
 * @fileOverview 2ch専用ブラウザ・クライアント用のライブラリ
 * @author Hiroyuki Sano
 */
(function() {
  'use strict';

  var root = this;

  define([
    'jquery',
    'underscore',
    'backbone',
    'http-lib',
    'cookie-manager',
    'parser',
    'encoding',
    'util-lib',
    'logger'
  ], function($, _, Backbone, HttpLib, CookieManager, Parser, encoding, UtilLib, Logger) {

    /**
     * @constructor Client
     */
    var Client = function(options, callback_context) {
      callback_context = callback_context || this;
      options = (options && options['client']) || options || {};

      /**
       * @description HTTPリクエスト時に渡すヘッダのリスト
       * @memberof Client
       *
       */
      this.HTTP_REQ_HEADERS_DEFAULT = {
        'User-Agent': 'Monazilla/1.00'
      };

      this.http = new HttpLib((options && options['http-lib']) || options, this);
      this.parser = new Parser((options && options['parser']) || options, this);
      this.logger = new Logger((options && options['logger']) || options, this);
      this.cookie_manager = new CookieManager((options && options['cookie-manager']) || options || {}, this);

      // Deferredの設定
      var keys = ['getThreadList', 'getSettingText', 'getResponsesFromThread'];
      _(keys)
        .each(function(key) {
          this[key] = UtilLib.getDeferredFunc(this[key], this, callback_context);
        }, this);

      // Deferredの設定（失敗する可能性があるもの）
      var keys = ['putResponseToThread'];
      _(keys)
        .each(function(key) {
          this[key] = UtilLib.getDeferredFuncWillFail(this[key], this, callback_context);
        }, this);
    };

    Client.prototype = {};
    var proto = _(Client.prototype);

    proto.extend({
      /**
       * @description スレッド一覧を取得する
       * @memberof Client
       *
       * @param {String} hostname
       * ホスト名
       * @param {String} board_id
       * 板ID
       * @param {Client#getThreadList-callback} callback
       * スレッド一覧取得後、callback(Array) として呼び出される。
       */
      getThreadList: function getThreadList(hostname, board_id, callback) {
        var url = GetUrl(hostname, '/' + board_id + '/subject.txt');
        this.http.get(
          url,
          _(this.HTTP_REQ_HEADERS_DEFAULT)
          .extend({
            'Host': hostname,
          }))
          .done(function(http_response) {
            callback(this.parser.parseThreadList(http_response.body));
          });
      }
    });

    /**
     * @description スレッド一覧取得後、callback(Array)として呼び出される
     * @callback Client#getThreadList-callback
     * @param {Array} thread_list
     * スレッド情報の配列
     */

    proto.extend({
      /**
       * @description [未実装] SETTING.TXTを取得する
       * @memberof Client
       *
       * @param {String} hostname
       * ホスト名
       * @param {String} board_id
       * 板ID
       * @param {Client#getSettingText-callback} callback
       * 情報取得後 callback(Object) として呼び出される
       */
      getSettingText: function getSettingText(hostname, board_id, callback) {
        var url = GetUrl(hostname, '/' + board_id + '/SETTING.TXT');
        this.http.get(
          url,
          _(this.HTTP_REQ_HEADERS_DEFAULT)
          .extend({
            'Host': hostname
          }))
          .done(function(http_response) {
            callback(parser.parseSettingText(http_response.body));
          });
      }
    });

    /**
     * @description 情報取得後 callback(Object)として呼び出される
     * @callback Client#getSettingText-callback
     * @param {Object} settings
     * 板の設定情報
     */

    proto.extend({
      /**
       * @description スレッドの書き込みを取得する
       * @memberof Client
       *
       * @param {String} hostname
       * ホスト名
       * @param {String} board_id
       * 板ID
       * @param {String} thread_id
       * スレッドID
       * @param {Client#getResponsesFromThread-callback} callback
       * 書き込み一覧を取得後 callback(Array) として呼び出される
       */
      getResponsesFromThread: function getResponsesFromThread(hostname, board_id, thread_id, callback) {
        var url = GetUrl(hostname, '/' + board_id + GetDatPath(hostname, thread_id));
        this.http.get(
          url,
          _(this.HTTP_REQ_HEADERS_DEFAULT)
          .extend({
            'Host': hostname
          }))
          .done(function(http_response) {
            callback(http_response);
          });
      }
    });

    /**
     * @description 書き込み一覧を取得後 callback(Array) として呼び出される
     * @callback Client#getResponsesFromThread-callback
     *
     * @param {Array} responses
     * スレッドに書き込まれたレスのリスト
     */

    proto.extend({
      // スレッドに書き込む
      /**
       * @description スレッドに書き込みを行う
       * @memberof Client
       *
       * @param {String} hostname
       * ホスト名
       * @param {String} board_id
       * 板ID
       * @param {String} thread_id
       * スレッドID
       * @param {Object} response
       * 書き込み内容
       * @param {Client#putResponseToThread-callback} callback
       * 書き込み完了後 callback(Object) として呼び出される
       */
      putResponseToThread: function putResponseToThread(hostname, board_id, thread_id, response, ok_callback, fail_callback) {
        var url = GetUrl(hostname, '/test/bbs.cgi?guid=ON');

        var http_req_headers = _(this.HTTP_REQ_HEADERS_DEFAULT)
          .extend({
            'Host': hostname,
            'Referer': GetUrl(hostname, '/' + board_id + '/')
          });

        // 書き込みを行う
        var write = function write(ok_callback, fail_callback) {

          // 書き込み送信後にHTTPレスポンスヘッダを受け取る
          var receive_response = function receive_response(http_response) {
            var after_receive_response = function after_receive_response() {
              if (/書き込みました/.test(http_response.body))
                ok_callback(http_response.body);
              else
                fail_callback(http_response.body);
            };
            after_receive_response = after_receive_response.bind(this);

            var deferreds = [];

            // HTTPレスポンスヘッダにSet-Cookieがある場合の処理
            if (typeof http_response.headers['Set-Cookie'] !== 'undefined') {
              console.log('@putResponseToThread::write:receive_response');
              deferreds.push(this.cookie_manager.setCookieHeader(url, http_response.headers_source));
            }

            // 各処理が終わったらputResponseToThreadとしての結果を返す
            $.when.apply(null, deferreds)
              .done(after_receive_response);
          };
          receive_response = receive_response.bind(this);

          // 書き込み内容などをSJISに変換する
          var converted_response = _(response)
            .clone();
          _(converted_response)
            .each(function(value, key) {
              converted_response[key] = ConvertToSJIS(value);
            });

          // 書き込み内容などをパーセントエンコーディングでエスケープする
          var escaped_response = _(converted_response)
            .clone();
          _(escaped_response)
            .each(function(value, key) {
              escaped_response[key] = EscapeSJIS(value);
            });

          // 準備ができたら書き込む
          // TODO: 利用規約などを確認させるための処理が組めるような流れもつくる
          this.http.post(
            url,
            http_req_headers, {
              'bbs': board_id,
              'key': thread_id,
              'time': 1,
              'submit': ConvertToSJIS('上記全てを承諾して書き込む'),
              'FROM': escaped_response.name,
              'mail': escaped_response.mail,
              'MESSAGE': escaped_response.body,
              'yuki': 'akari'
            })
            .done(receive_response);
        };

        write = write.bind(this);

        var deferreds = $.when.apply(null, [
          this.cookie_manager.getCookieHeader(url)
          .done(function(cookie_header) {
            if (cookie_header === 'Cookie: ')
              return;
            _(http_req_headers)
              .extend({
                'Cookie': cookie_header.substr(7)
              });

          })
        ]);
        deferreds.done(function() {
          write(ok_callback, fail_callback);
        });
      }
    });

    /**
     * @description 書き込み完了後 callback(Object) として呼び出される
     * @callback Client#putResponseToThread-callback
     *
     * @param {Object} http_response
     * HTTPレスポンス
     */

    // ホスト名とパスを繋げたURLを返す

    function GetUrl(hostname, path) {
      return 'http://' + hostname + path;
    }

    // DATファイルのパスを返す

    function GetDatPath(hostname, thread_id) {
      return '/dat/' + thread_id + '.dat';
    }

    // 与えられた文字列をSJISに変換する

    function ConvertToSJIS(str) {
      return encoding.codeToString(encoding.convert(GetArray(str), 'SJIS', 'AUTO'));
    }

    // 与えられた文字列をSJISに変換する

    function ConvertToUTF8(str) {
      return encoding.codeToString(encoding.convert(GetArray(str), 'UTF-8', 'SJIS'));
    }

    // 与えられた文字列をSJISに変換する

    function EscapeSJIS(str) {
      return _(str)
        .map(function(c) {
          return '%' + c.charCodeAt()
            .toString(16)
            .toUpperCase();
        })
        .join('');
    }

    // 文字列を配列に変換する

    function GetArray(str) {
      var res = [];
      _(str.split(''))
        .each(function(c) {
          res.push(c.charCodeAt());
        });
      return res;
    }

    return Client;
  });

})();
<commit_msg>Issue #8: refactoring<commit_after>/* ===================================================
 * JavaScript 2ch-client Library
 * https://github.com/sh19910711/js2ch
 * ===================================================
 * Copyright (c) 2013 Hiroyuki Sano
 *
 * Licensed under MIT License.
 * http://opensource.org/licenses/MIT
 * =================================================== */
/**
 * @fileOverview 2ch専用ブラウザ・クライアント用のライブラリ
 * @author Hiroyuki Sano
 */
(function() {
  'use strict';

  var root = this;

  define([
    'jquery',
    'underscore',
    'backbone',
    'http-lib',
    'cookie-manager',
    'parser',
    'encoding',
    'util-lib',
    'logger'
  ], function($, _, Backbone, HttpLib, CookieManager, Parser, encoding, UtilLib, Logger) {

    /**
     * @constructor Client
     */
    var Client = function(options, callback_context) {
      callback_context = callback_context || this;
      options = (options && options['client']) || options || {};

      /**
       * @description HTTPリクエスト時に渡すヘッダのリスト
       * @memberof Client
       *
       */
      this.HTTP_REQ_HEADERS_DEFAULT = {
        'User-Agent': 'Monazilla/1.00'
      };

      this.http = new HttpLib((options && options['http-lib']) || options, this);
      this.parser = new Parser((options && options['parser']) || options, this);
      this.logger = new Logger((options && options['logger']) || options, this);
      this.cookie_manager = new CookieManager((options && options['cookie-manager']) || options || {}, this);

      // Deferredの設定
      var keys = ['getThreadList', 'getSettingText', 'getResponsesFromThread'];
      _(keys)
        .each(function(key) {
          this[key] = UtilLib.getDeferredFunc(this[key], this, callback_context);
        }, this);

      // Deferredの設定（失敗する可能性があるもの）
      var keys = ['putResponseToThread'];
      _(keys)
        .each(function(key) {
          this[key] = UtilLib.getDeferredFuncWillFail(this[key], this, callback_context);
        }, this);
    };

    Client.prototype = {};
    var proto = _(Client.prototype);

    proto.extend({
      /**
       * @description スレッド一覧を取得する
       * @memberof Client
       *
       * @param {String} hostname
       * ホスト名
       * @param {String} board_id
       * 板ID
       * @param {Client#getThreadList-callback} callback
       * スレッド一覧取得後、callback(Array) として呼び出される。
       */
      getThreadList: function getThreadList(hostname, board_id, callback) {
        var url = GetUrl(hostname, '/' + board_id + '/subject.txt');
        this.http.get(
          url,
          _(this.HTTP_REQ_HEADERS_DEFAULT)
          .extend({
            'Host': hostname,
          }))
          .done(function(http_response) {
            callback(this.parser.parseThreadList(http_response.body));
          });
      }
    });

    /**
     * @description スレッド一覧取得後、callback(Array)として呼び出される
     * @callback Client#getThreadList-callback
     * @param {Array} thread_list
     * スレッド情報の配列
     */

    proto.extend({
      /**
       * @description [未実装] SETTING.TXTを取得する
       * @memberof Client
       *
       * @param {String} hostname
       * ホスト名
       * @param {String} board_id
       * 板ID
       * @param {Client#getSettingText-callback} callback
       * 情報取得後 callback(Object) として呼び出される
       */
      getSettingText: function getSettingText(hostname, board_id, callback) {
        var url = GetUrl(hostname, '/' + board_id + '/SETTING.TXT');
        var http_request_headers = this.HTTP_REQ_HEADERS_DEFAULT;

        _(http_request_headers)
          .extend({
            'Host': hostname
          });

        this.http.get(url, http_request_headers)
          .done(function(http_response) {
            callback(parser.parseSettingText(http_response.body));
          });
      }
    });

    /**
     * @description 情報取得後 callback(Object)として呼び出される
     * @callback Client#getSettingText-callback
     * @param {Object} settings
     * 板の設定情報
     */

    proto.extend({
      /**
       * @description スレッドの書き込みを取得する
       * @memberof Client
       *
       * @param {String} hostname
       * ホスト名
       * @param {String} board_id
       * 板ID
       * @param {String} thread_id
       * スレッドID
       * @param {Client#getResponsesFromThread-callback} callback
       * 書き込み一覧を取得後 callback(Array) として呼び出される
       */
      getResponsesFromThread: function getResponsesFromThread(hostname, board_id, thread_id, callback) {
        var url = GetUrl(hostname, '/' + board_id + GetDatPath(hostname, thread_id));
        this.http.get(
          url,
          _(this.HTTP_REQ_HEADERS_DEFAULT)
          .extend({
            'Host': hostname
          }))
          .done(function(http_response) {
            callback(http_response);
          });
      }
    });

    /**
     * @description 書き込み一覧を取得後 callback(Array) として呼び出される
     * @callback Client#getResponsesFromThread-callback
     *
     * @param {Array} responses
     * スレッドに書き込まれたレスのリスト
     */

    proto.extend({
      // スレッドに書き込む
      /**
       * @description スレッドに書き込みを行う
       * @memberof Client
       *
       * @param {String} hostname
       * ホスト名
       * @param {String} board_id
       * 板ID
       * @param {String} thread_id
       * スレッドID
       * @param {Object} response
       * 書き込み内容
       * @param {Client#putResponseToThread-callback} callback
       * 書き込み完了後 callback(Object) として呼び出される
       */
      putResponseToThread: function putResponseToThread(hostname, board_id, thread_id, response, ok_callback, fail_callback) {
        var url = GetUrl(hostname, '/test/bbs.cgi?guid=ON');

        var http_req_headers = _(this.HTTP_REQ_HEADERS_DEFAULT)
          .extend({
            'Host': hostname,
            'Referer': GetUrl(hostname, '/' + board_id + '/')
          });

        // 書き込みを行う
        var write = function write(ok_callback, fail_callback) {
          // 書き込み送信後にHTTPレスポンスヘッダを受け取る
          var receive_response = function receive_response(http_response) {
            // HTTPレスポンス受信後の処理（callbackの実行）
            var after_receive_response = function after_receive_response() {
              // TODO: 利用規約などを確認させるための処理が組めるような流れもつくる
              if (/書き込みました/.test(http_response.body))
                ok_callback(http_response.body);
              else
                fail_callback(http_response.body);
            };
            after_receive_response = after_receive_response.bind(this);

            var promises = [];

            // HTTPレスポンスヘッダにSet-Cookieがある場合の処理
            if (typeof http_response.headers['Set-Cookie'] !== 'undefined') {
              promises.push(this.cookie_manager.setCookieHeader(url, http_response.headers_source));
            }

            // 各処理が終わったらputResponseToThreadとしての結果を返す
            $.when.apply(null, promises)
              .done(after_receive_response);
          };
          receive_response = receive_response.bind(this);

          // 書き込み内容などをSJISに変換する
          var converted_response = _(response)
            .clone();
          _(converted_response)
            .each(function(value, key) {
              converted_response[key] = ConvertToSJIS(value);
            });

          // 書き込み内容などをパーセントエンコーディングでエスケープする
          var escaped_response = _(converted_response)
            .clone();
          _(escaped_response)
            .each(function(value, key) {
              escaped_response[key] = EscapeSJIS(value);
            });

          // 送信するデータ
          var http_req_params = {
            'bbs': board_id,
            'key': thread_id,
            'time': 1,
            'submit': ConvertToSJIS('上記全てを承諾して書き込む'),
            'FROM': escaped_response.name,
            'mail': escaped_response.mail,
            'MESSAGE': escaped_response.body,
            'yuki': 'akari'
          };

          // 準備ができたらPOSTリクエストを送信する
          this.http.post(url, http_req_headers, http_req_params)
            .done(receive_response);
        };
        write = write.bind(this);

        // 取得したCookieをHTTPリクエストヘッダに追加する
        var after_get_cookie_header = function after_get_cookie_header_func(cookie_header) {
          if (cookie_header === 'Cookie: ')
            return;
          _(http_req_headers)
            .extend({
              'Cookie': cookie_header.substr(7)
            });
        };

        // リクエスト前の準備
        var deferreds = $.when.apply(null, [
          this.cookie_manager.getCookieHeader(url)
          .done(after_get_cookie_header)
        ]);

        // 準備ができたらwriteを実行する
        deferreds.done(function() {
          write(ok_callback, fail_callback);
        });
      }
    });

    /**
     * @description 書き込み完了後 callback(Object) として呼び出される
     * @callback Client#putResponseToThread-callback
     *
     * @param {Object} http_response
     * HTTPレスポンス
     */

    // ホスト名とパスを繋げたURLを返す

    function GetUrl(hostname, path) {
      return 'http://' + hostname + path;
    }

    // DATファイルのパスを返す

    function GetDatPath(hostname, thread_id) {
      return '/dat/' + thread_id + '.dat';
    }

    // 与えられた文字列をSJISに変換する

    function ConvertToSJIS(str) {
      return encoding.codeToString(encoding.convert(GetArray(str), 'SJIS', 'AUTO'));
    }

    // 与えられた文字列をSJISに変換する

    function ConvertToUTF8(str) {
      return encoding.codeToString(encoding.convert(GetArray(str), 'UTF-8', 'SJIS'));
    }

    // 与えられた文字列をSJISに変換する

    function EscapeSJIS(str) {
      return _(str)
        .map(function(c) {
          return '%' + c.charCodeAt()
            .toString(16)
            .toUpperCase();
        })
        .join('');
    }

    // 文字列を配列に変換する

    function GetArray(str) {
      var res = [];
      _(str.split(''))
        .each(function(c) {
          res.push(c.charCodeAt());
        });
      return res;
    }

    return Client;
  });

})();
<|endoftext|>