Skip to content

Instantly share code, notes, and snippets.

@motemen
Created February 16, 2009 13:11
Show Gist options
  • Save motemen/65160 to your computer and use it in GitHub Desktop.
Save motemen/65160 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Hatena::Diary - Autocomplete Categories
// @namespace http://d.hatena.ne.jp/motemen/
// @include http://d.hatena.ne.jp/*
// @include http://*.g.hatena.ne.jp/*
// @include https://*.g.hatena.ne.jp/*
// ==/UserScript==
with (unsafeWindow) {
(function() {
if (typeof unsafeWindow.Ten == 'undefined' || typeof unsafeWindow.Ten.Class == 'undefined' || typeof Hatena == 'undefined' || typeof Hatena.Diary == 'undefined' || typeof Hatena.Diary.EditInPlace == 'undefined')
return setTimeout(arguments.callee, 500);
var AutoCompleter = function(input, words, options) {
this.input = input;
this.words = words;
this.options = options || { };
input.blur();
input.setAttribute('autocomplete', 'off');
input.focus();
var pos = Ten.Geometry.getElementPosition(input);
this.container = Ten.Element('ul', { style: AutoCompleter.CONTAINER_STYLE });
document.body.appendChild(this.container);
Ten.Style.applyStyle(
this.container, {
left: pos.x + 'px',
top: pos.y + input.offsetHeight + 'px',
width: input.offsetWidth + 'px',
fontFamily: Ten.Style.getElementStyle(input, 'fontFamily')
}
);
var self = this;
var keyHandler = function(e) { self.keyHandler(e) };
var hide = function(e) { self.hide(e) };
input.addEventListener('keyup', keyHandler, true);
input.addEventListener('keypress', keyHandler, true);
input.addEventListener('focus', keyHandler, true);
input.addEventListener('blur', hide, true);
this.lastInput = null;
this.index = 0;
this.updateCanditates();
this.updateSelection();
};
AutoCompleter.CONTAINER_STYLE = {
position: 'absolute',
padding: '0px',
margin: '0px',
listStyle: 'none',
textAlign: 'left',
backgroundColor: '#FFF',
border: '1px solid #BBC4CD'
};
AutoCompleter.CANDITATE_STYLE = {
fontSize: '90%',
padding: '0.3em',
overflow: 'hidden',
borderBottom: '1px dotted #999'
};
AutoCompleter.HIGHLIGHT_STYLE = {
color: 'highlighttext',
backgroundColor: 'highlight'
};
// TAB: 9
// zk : 38
// zj : 40
AutoCompleter.prototype = {
keyHandler: function(e) {
if (!this.isHidden() && e.type == 'keypress') {
switch (true) {
case e.keyCode == 38:
case e.keyCode == 9 && e.shiftKey:
this.selectPrev();
e.preventDefault();
return;
case e.keyCode == 40:
case e.keyCode == 9:
this.selectNext();
e.preventDefault();
return;
case e.keyCode == 13:
case String.fromCharCode(e.charCode) == ']' && e.ctrlKey:
this.enter();
e.preventDefault();
return;
}
}
this.checkInput();
},
checkInput: function() {
var text = this.input.value;
var sel = this.input.selectionEnd;
var left = text.slice(0, sel), right = text.slice(sel);
var filter = this.options.filter;
if (filter) {
if (filter.exec) {
var match = filter.exec(left);
if (match) {
var input = match[0];
this.leftContext = RegExp.leftContext;
this.rightContext = RegExp.rightContext;
} else {
var input = '';
this.leftContext = '';
this.rightContext = '';
}
this.rightContext += right;
} else {
var match = filter(left, right);
if (match) {
var input = match.input;
this.leftContext = match.leftContext;
this.rightContext = match.rightContext;
} else {
var input = '';
this.leftContext = '';
this.rightContext = '';
}
}
} else {
var input = left;
this.leftContext = '';
this.rightContext = right;
}
if (input == this.lastInput)
return;
this.lastInput = input;
var canditates = [];
if (input) {
input = input.toUpperCase();
for (var i = 0, words = this.words; i < words.length; i++) {
if (words[i].toUpperCase().indexOf(input) == 0) {
canditates.push(words[i]);
}
}
}
if (canditates.length == 1 && canditates[0] == this.input.value) {
this.hide();
} else {
this.index = 0;
this.updateCanditates(canditates);
this.updateSelection();
}
},
selectPrev: function() {
var len = this.canditates.length;
this.index = (this.index - 1 + len) % len;
this.updateSelection();
},
selectNext: function() {
var len = this.canditates.length;
this.index = (this.index + 1) % len;
this.updateSelection();
},
enter: function() {
this.leftContext = this.leftContext || '';
this.rightContext = this.rightContext || '';
this.input.value = this.leftContext + this.canditates[this.index] + this.rightContext;
var text = this.leftContext + this.canditates[this.index];
this.input.setSelectionRange(text.length, text.length);
this.hide();
},
hide: function() {
this.container.style.display = 'none';
},
isHidden: function() {
return this.container.style.display == 'none';
},
close: function() {
Ten.DOM.removeElement(this.container);
if (this.textWidthElement)
Ten.DOM.removeElement(this.textWidthElement);
},
updateCanditates: function(canditates) {
this.canditates = canditates = canditates || [];
var container = this.container;
while (container.firstChild)
Ten.DOM.removeElement(container.firstChild);
container.style.display = canditates.length ? '' : 'none';
for (var i = 0; i < canditates.length; i++) {
container.appendChild(Ten.Element('li', { style: AutoCompleter.CANDITATE_STYLE }, canditates[i]));
}
this.updateContainerPosition();
},
updateSelection: function() {
this.selected && Ten.Style.applyStyle(this.selected, { color: '', backgroundColor: ''});
this.selected = this.container.getElementsByTagName('li')[this.index];
this.selected && Ten.Style.applyStyle(this.selected, AutoCompleter.HIGHLIGHT_STYLE);
},
updateContainerPosition: function() {
var pos = Ten.Geometry.getElementPosition(this.input);
var left = pos.x + (this.leftContext ? this.calculateTextWidth(this.leftContext) : '');
this.container.style.left = left + 'px';
this.container.style.width = this.input.offsetWidth - left + 'px';
},
calculateTextWidth: function(text) {
if (!this.textWidthElement) {
var KEEP_STYLES = {
padding: 'top left bottom right',
margin: 'top left bottom right',
font: 'size family weight'
};
var span = Ten.Element('span', { style: { position: 'absolute', visibility: 'hidden' } });
for (var s in KEEP_STYLES) {
var cs = KEEP_STYLES[s].split(/ /);
for (var i = 0; i < cs.length; i++) {
var attr = s + cs[i].replace(/^./, function(s) { return s.toUpperCase() });
span.style[attr] = Ten.Style.getElementStyle(this.input, attr)
}
}
document.body.appendChild(span);
this.textWidthElement = span;
}
this.textWidthElement.textContent = text;
return this.textWidthElement.offsetWidth;
}
};
var categories = [];
for (var i = 0; i < Hatena.Diary.Categories.length; i++)
categories.push('[' + Hatena.Diary.Categories[i] + ']');
if (document.location.hash == '#edit_in_place')
attachAutoCompleter(document.forms[document.forms.length - 1]);
Hatena.Diary.EditInPlace.Form.addEventListener('create', attachAutoCompleter);
unsafeWindow.attachAutoCompleter = attachAutoCompleter;
function attachAutoCompleter(form) {
if (!form._autocompleter)
form._autocompleter = new AutoCompleter(form.title, categories, { filter: filter });
}
function filter(left, right) {
if (left.match(/^(?:\[[^\]]+\])*\[[^\]]*$/) && !right.match(/^(?:\[[^\]]+\])*[^\[\]]*\]/)) {
var m = left.match(/^((?:\[[^\]]+\])*)(\[[^\]]*)$/);
return {
leftContext: m[1],
input: m[2],
rightContext: right
};
}
}
})();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment