Created
July 30, 2012 22:58
-
-
Save sbp/3211560 to your computer and use it in GitHub Desktop.
Edit a DOM using UI controls with contenteditable
This file contains hidden or 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
node_name = (node) -> | |
return node.nodeName.toLowerCase() | |
current_range = () -> | |
if window.getSelection | |
selection = window.getSelection() | |
if selection.getRangeAt and selection.rangeCount | |
return selection.getRangeAt 0 | |
set_range = (range) -> | |
selection = window.getSelection() | |
selection.removeAllRanges() | |
selection.addRange range | |
# refreshed = document.createRange() | |
# refreshed.setStart range.startContainer, range.startOffset | |
# refreshed.collapse true | |
UNUSED_clone_range = (range) -> | |
clone = document.createRange() | |
clone.setStart $(range.startContainer)[0], range.startOffset | |
clone.collapse true | |
return clone | |
current_node = () -> | |
range = current_range() | |
return range.startContainer | |
current_element = () -> | |
node = current_node() | |
if node.parentElement | |
return node.parentElement | |
# otherwise, loop to find node.nodeType == 1? | |
current_tree = () -> | |
tree = [] | |
node = current_node() | |
while node.parentElement | |
node = node.parentElement | |
tree.push node | |
return tree | |
current_tree_names = () -> | |
names = [] | |
tree = current_tree() | |
for node in tree | |
name = node_name node | |
names.push name | |
return names | |
current_block = () -> | |
blocks = | |
p: true | |
h1: true | |
h2: true | |
h3: true | |
h4: true | |
tree = current_tree() | |
for node in tree | |
name = node_name node | |
if name of blocks | |
return node | |
child_index = (node) -> | |
index = 0 | |
for child in node.parentNode.childNodes | |
if child is node | |
return index | |
index++ | |
current_indices = () -> | |
indices = [] | |
node = current_node() | |
block = current_block() | |
while node != block | |
index = child_index node | |
indices.push index | |
node = node.parentNode | |
return indices | |
transform_current_block = (name) -> | |
indices = current_indices() | |
offset = current_range().startOffset | |
block = $ current_block() | |
dummy = $ "<span></span>" | |
block.append dummy | |
block.wrap "<#{name}/>" | |
transformation = block.parent() | |
dummy.unwrap() | |
dummy.remove() | |
container = transformation[0] | |
while indices.length | |
index = indices.pop() | |
container = container.childNodes[index] | |
range = document.createRange() | |
range.setStart container, offset | |
range.collapse true | |
set_range range | |
transform_to_heading = () -> | |
headings = | |
h1: "h2" | |
h2: "h3" | |
h3: "h4" | |
h4: "h1" | |
block_name = node_name current_block() | |
if block_name of headings | |
name = headings[block_name] | |
else name = "h1" | |
transform_current_block name | |
transform_to_paragraph = () -> | |
block_name = node_name current_block() | |
if block_name isnt "p" | |
transform_current_block "p" | |
UNUSED_split_text_node = (node, offset) -> | |
node = $ node | |
text = node.text() | |
a = text.substring 0, offset | |
b = text.substring offset, text.length | |
node.text a | |
b_node = $ "" | |
b_node.insertAfter node | |
b_node.text b | |
return [node, b_node] | |
insert_into_text = (text, offset, node) -> | |
text = $ text | |
text_content = text.text() | |
# if offset is 0 | |
# return text.before node | |
# if offset is text_content.length | |
# return text.after node | |
before = text_content.substring 0, offset | |
after = text_content.substring offset | |
form = $ "<form> </form>" | |
form.css "display": "inline" | |
input = $ "<input/>" | |
input.css | |
"border": "none" | |
"border-bottom": "1px solid #ccc" | |
form.submit -> | |
node.attr "href", input.val() | |
form.remove() | |
caret = document.createRange() | |
children = node[0].childNodes | |
end = children[children.length - 1] | |
caret.setStart end, end.length | |
caret.collapse | |
set_range caret | |
adjacent_shim() | |
return false | |
form.append input | |
text.after before, node, form, after | |
text.remove() | |
input.focus() | |
apply_highlight = () -> | |
element = $ current_element() | |
if (element.attr "data-style-backup") | |
if (element.attr "data-style-backup") isnt "" | |
return | |
style = element.attr "style" | |
console.log style | |
if style | |
console.log "backing up style" | |
element.attr "data-style-backup", element.attr "style" | |
element.css | |
"background-color": "rgba(128, 192, 0, 0.25)" | |
"outline": "1px solid #9c0" | |
"outline-offset": "0" | |
editing = () -> | |
return ($("body").attr "contenteditable") is "true" | |
remove_highlight = () -> | |
element = $ current_element() | |
if (element.attr "data-style-backup") | |
if (element.attr "data-style-backup") isnt "" | |
element.attr "style", element.attr "data-style-backup" | |
element.removeAttr "data-style-backup" | |
return | |
element.removeAttr "style" | |
# element.css | |
# "background-color": "inherit" | |
# "outline": "none" | |
# "outline-offset": "0" | |
transform_to_anchor = () -> | |
range = current_range() | |
contents = range.extractContents() | |
anchor = $ "<a href=\"#\"></a>" | |
insert_into_text range.startContainer, range.startOffset, anchor | |
anchor.append contents | |
if false | |
console.log "not contents" | |
arange = document.createRange() | |
arange.setStart anchor, 0 | |
arange.collapse true | |
set_range arange | |
create_status_bar = () -> | |
status_bar = $("<div>—</div>") | |
status_bar.attr "id", "statusbar" | |
status_bar.attr "contenteditable", "false" | |
status_bar.css | |
"position": "fixed" | |
"bottom": "12px" | |
"right": "12px" | |
"padding": "3px" | |
"background-color": "rgba(192, 192, 192, 0.25)" | |
"border-radius": "3px" | |
"box-shadow": "0 0 12px rgba(192, 192, 192, 0.25)" | |
"min-width": "180px" | |
"text-align": "right" | |
$("body").append status_bar | |
update_status_bar = () -> | |
if not current_range() | |
return | |
node = current_node() | |
names = [] | |
number = 0 | |
while node.parentElement and (number < 3) | |
if node.nodeType is 1 | |
names.push node_name node | |
number++ | |
node = node.parentElement | |
$("#statusbar").text "editing: " | |
while names.length | |
$("#statusbar").append names.pop() | |
if names.length | |
$("#statusbar").append " > " | |
remove_status_bar = () -> | |
$("#statusbar").remove() | |
window.editing_element = null | |
UNUSED_inline_shim = () -> | |
inline = | |
a: true | |
element = current_element() | |
element_name = node_name element | |
if element isnt window.editing_element | |
if element_name of inline | |
e = $ element | |
e.prepend "[" | |
e.append "]" | |
range = current_range() | |
r = document.createRange() | |
r.setStart range.startContainer, range.startOffset - 1 # um, or +1 | |
r.collapse true | |
set_range r | |
if window.editing_element | |
if (node_name window.editing_element) of inline | |
e = $ editing_element | |
e.css "background-color": "green" | |
window.editing_element = element | |
window.adjacentOld = [] | |
window.adjacentNew = [] | |
adjacent = (node) -> | |
window.adjacentNew.push node | |
add_anchor_shim = (node) -> | |
open = document.createTextNode "[" | |
close = document.createTextNode "]" | |
node = $ node | |
node.prepend open | |
node.append close | |
without = (array, element) -> | |
index = $.inArray element, array | |
delete array[index] | |
return $.map array, (f) -> f | |
contains = (array, element) -> | |
return ($.inArray element, array) > -1 | |
remove_anchor_shim = (node) -> | |
window.adjacentNew = without window.adjacentNew, node | |
for child in node.childNodes | |
continue if not child | |
continue if not child.textContent | |
if (child.textContent is "[") or (child.textContent is "]") | |
$(child).remove() | |
update_adjacent = (node) -> | |
# Old AND New | |
console.log "window.adjacentNew:", window.adjacentNew | |
for ne in window.adjacentNew | |
if contains window.adjacentOld, ne | |
console.log "Old AND New:", ne.textContent | |
window.adjacentOld = without window.adjacentOld, ne | |
# New - Old | |
else | |
add_anchor_shim ne | |
console.log "New - Old:", ne.textContent | |
# Old - New | |
for old in window.adjacentOld | |
remove_anchor_shim old | |
console.log "Old - New:", old.textContent | |
window.adjacentOld = window.adjacentNew | |
window.adjacentNew = [] | |
adjacent_shim = () -> | |
range = current_range() | |
node = current_node() | |
element = current_element() | |
console.log range.startOffset, range.startContainer.textContent | |
if range.startOffset is 0 | |
console.log "Start of node:", node_name element | |
sibling = node.previousSibling | |
if sibling | |
if (node_name sibling) is "a" | |
console.log "Adjacent:", sibling.textContent | |
adjacent sibling | |
if range.startOffset is node.length | |
console.log "End of node:", node_name element | |
if (node_name element) is "a" | |
console.log "Adjacent:", element.textContent | |
adjacent element | |
console.log "Current:", element.childNodes[element.childNodes.length - 1] | |
if element.childNodes[element.childNodes.length - 1] is node | |
sibling = element.nextSibling | |
else sibling = node.nextSibling | |
else sibling = node.nextSibling | |
if sibling | |
if (node_name sibling) is "a" | |
console.log "Adjacent:", sibling.textContent | |
adjacent sibling | |
else | |
if (node_name element) is "a" | |
adjacent element | |
# else window.adjacentNew = window.adjacentOld | |
console.log "Updating adjacent" | |
update_adjacent() | |
$ -> | |
$("body").keydown (e) -> | |
if e.ctrlKey and e.keyCode != 17 | |
console.log e.keyCode | |
if e.ctrlKey and e.keyCode is 69 # Ctrl+E | |
if editing() | |
$("body").attr "contenteditable", "false" | |
remove_status_bar() | |
else | |
$("body").attr "contenteditable", "true" | |
create_status_bar() | |
e.preventDefault() | |
return if not editing() | |
if e.ctrlKey and e.keyCode is 72 # Ctrl+H | |
transform_to_heading() | |
e.preventDefault() | |
if e.ctrlKey and e.keyCode is 80 # Ctrl+P | |
transform_to_paragraph() | |
e.preventDefault() | |
if e.ctrlKey and e.keyCode is 65 # Ctrl+A | |
transform_to_anchor() | |
e.preventDefault() | |
if e.ctrlKey and e.keyCode is 186 # Ctrl+; | |
apply_highlight() | |
e.preventDefault() | |
$("body").keyup (e) -> | |
return if not editing() | |
update_status_bar() | |
adjacent_shim() | |
if e.ctrlKey and e.keyCode is 186 # Ctrl+; | |
remove_highlight() | |
e.preventDefault() | |
$("body").mousedown (e) -> | |
return if not editing() | |
$("body").mouseup (e) -> | |
return if not editing() | |
update_status_bar() | |
adjacent_shim() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment