Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

New Paragraph on Enter event (with sleep) #233

Merged
merged 6 commits into from

2 participants

@hallahan

pair programming at PIE

enter button with paragraphs produces another text area and gains focus

@WardCunningham WardCunningham merged commit 4a0c727 into WardCunningham:master
@WardCunningham

@hallaha showed this during our video chat last wednesday. He and I got together here in Portland Wednesday to walk through the code and get it in shape for a pull request. In doing so we arrived at two important realizations:

  • At the point that one paragraph is closed and another is opened we find ourselves sending two asynchronous requests to the server to process these two events. The results were sometimes corrupted to where a page could not be later displayed without throwing exceptions. We also showed that by inserting sub-second delays we could process these requests in either order and get reliable results. We've left the more logical of these in place. However, we feel that the proper solution would be to serialize the requests via callbacks in the client code, OR, recognize in the client that the two requests are really just one (an add, not an add + edit) and do only one request.

  • Although we were offered interesting alternatives for signaling a paragraph break (blank line vs. single return) and when that signal would be interpreted (on keystroke vs. on save), we agreed that we were allowed to try what we considered the simplest approach especially because the decision could be easily reversed after gaining some experience. This freedom does not apply for decisions that leave a trail in the database. This mod is only a keyboard shortcut to functionality already present.

@hallahan
@WardCunningham

Are you thinking that if I were to hit a series of return keystrokes then I should have created a series of empty paragraphs?

One might do this if they wanted a lot of blank space on the page.

Right now we consider saving an empty paragraph as a request to have the item deleted.

@hallahan

Nah, I see no reason to have a bunch of white space. I'm talking about if the caret is at 0 and we have text in the box after it. This is useful if I double click on a paragraph and I want to make a new paragraph after it.

@WardCunningham

Maybe what is required is to have the caret placed at the end of a paragraph when one is double-clicked?

@hallahan

I agree, however, a backspace should still go to the last item... I have found a strange bug. The flag is this weird parallel line glyph in Chrome in Windows 7. On mouse over, it is a rectangle. It's interesting to see. Boot up Windows and have a look. It is more fork like, but less country like... I like using the symbols for icons, but I think we need to load in an explicit font so that things are the same everywhere.

@WardCunningham

Handing the backspace in a consistent way would seem similar to the split-at-the-caret functionality we currently have. The preconditions for special backspace handling would be:

  • The TextEditor is editing an item of type==paragraph.
  • The caret is at postion 0 (beginning)
  • The preceding item is also type==paragraph

A backspace in this situation would:

  • Concatenate the text from both paragraphs
  • Remove the second paragraph (tell server)
  • Replace the text of the first paragraph with the concatenation (tell the server, if changed)
  • Open the TextEditor on the first paragraph
  • Position the TextEditor caret at the point where the joined texts meet.

Note that the paragraphs joined by this mechanism need not have been previously split. A reasonable refactoring would be to split a paragraph, drag something into the gap, then join the pieces back together.

Admittedly, a common case will be that of hitting return by habit, recognizing that it was unintentional, and then backspacing to return to the desired state.

@hallahan

That last step of positioning the caret at the end of the text after a backspace concatenation may be weird. This will mean that the caret will jump from where it was to the end. Maybe just concatenate but leave the caret where it is? I'll have to implement this and try out both.

@WardCunningham

@hallahan's right. The cursor should go to exactly the point between the joined texts. (What was I thinking?)

@hallahan

done, see pull request.

@WardCunningham

Awesome. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
1  .gitignore
@@ -1,5 +1,6 @@
.DS_Store
*.swp
+*.iml
.idea/
.sass-cache
.rvmrc
View
77 client/client.js
@@ -368,7 +368,7 @@ require.define("/lib/legacy.coffee", function (require, module, exports, __dirna
};
$(function() {
- var LEFTARROW, RIGHTARROW, addToJournal, createPage, doInternalLink, finishClick, getItem, resolveFrom, resolveLinks, textEditor, useLocalStorage;
+ var LEFTARROW, RIGHTARROW, addToJournal, createPage, createTextElement, doInternalLink, finishClick, getItem, resolveFrom, resolveLinks, textEditor, useLocalStorage;
window.dialog = $('<div></div>').html('This dialog will show every time!').dialog({
autoOpen: false,
title: 'Basic Dialog',
@@ -454,6 +454,30 @@ require.define("/lib/legacy.coffee", function (require, module, exports, __dirna
wiki.log('useLocalStorage', $(".login").length > 0);
return $(".login").length > 0;
};
+ createTextElement = function(pageElement, beforeElement) {
+ var item, itemBefore, itemElement, sleep;
+ item = {
+ type: 'paragraph',
+ id: util.randomBytes(8)
+ };
+ itemElement = $("<div class=\"item paragraph\" data-id=" + item.id + "></div>");
+ itemElement.data('item', item).data('pageElement', pageElement);
+ beforeElement.after(itemElement);
+ plugin["do"](itemElement, item);
+ itemBefore = wiki.getItem(beforeElement);
+ wiki.textEditor(itemElement, item);
+ sleep = function(time, code) {
+ return setTimeout(code, time);
+ };
+ return sleep(500, function() {
+ return pageHandler.put(pageElement, {
+ item: item,
+ id: item.id,
+ type: 'add',
+ after: itemBefore != null ? itemBefore.id : void 0
+ });
+ });
+ };
textEditor = wiki.textEditor = function(div, item) {
var original, textarea, _ref;
textarea = $("<textarea>" + (original = (_ref = item.text) != null ? _ref : '') + "</textarea>").focusout(function() {
@@ -474,10 +498,17 @@ require.define("/lib/legacy.coffee", function (require, module, exports, __dirna
}
return null;
}).bind('keydown', function(e) {
+ var pageElement;
if ((e.altKey || e.ctlKey || e.metaKey) && e.which === 83) {
textarea.focusout();
return false;
}
+ if (e.which === $.ui.keyCode.ENTER) {
+ textarea.focusout();
+ pageElement = div.parent().parent();
+ createTextElement(pageElement, div);
+ return false;
+ }
}).bind('dblclick', function(e) {
return false;
});
@@ -1204,7 +1235,7 @@ require.define("/lib/plugin.coffee", function (require, module, exports, __dirna
require.define("/lib/refresh.coffee", function (require, module, exports, __dirname, __filename) {
(function() {
- var emitHeader, handleDragging, initAddButton, initDragging, pageHandler, plugin, refresh, state, util;
+ var createFactory, emitHeader, handleDragging, initAddButton, initDragging, pageHandler, plugin, refresh, state, util;
util = require('./util.coffee');
@@ -1255,26 +1286,30 @@ require.define("/lib/refresh.coffee", function (require, module, exports, __dirn
initAddButton = function(pageElement) {
return pageElement.find(".add-factory").live("click", function(evt) {
- var before, beforeElement, item, itemElement;
evt.preventDefault();
- item = {
- type: "factory",
- id: util.randomBytes(8)
- };
- itemElement = $("<div />", {
- "class": "item factory"
- }).data('item', item).attr('data-id', item.id);
- itemElement.data('pageElement', pageElement);
- pageElement.find(".story").append(itemElement);
- plugin["do"](itemElement, item);
- beforeElement = itemElement.prev('.item');
- before = wiki.getItem(beforeElement);
- return pageHandler.put(pageElement, {
- item: item,
- id: item.id,
- type: "add",
- after: before != null ? before.id : void 0
- });
+ return createFactory(pageElement);
+ });
+ };
+
+ createFactory = function(pageElement) {
+ var before, beforeElement, item, itemElement;
+ item = {
+ type: "factory",
+ id: util.randomBytes(8)
+ };
+ itemElement = $("<div />", {
+ "class": "item factory"
+ }).data('item', item).attr('data-id', item.id);
+ itemElement.data('pageElement', pageElement);
+ pageElement.find(".story").append(itemElement);
+ plugin["do"](itemElement, item);
+ beforeElement = itemElement.prev('.item');
+ before = wiki.getItem(beforeElement);
+ return pageHandler.put(pageElement, {
+ item: item,
+ id: item.id,
+ type: "add",
+ after: before != null ? before.id : void 0
});
};
View
23 client/lib/legacy.coffee
@@ -101,6 +101,23 @@ $ ->
wiki.log 'useLocalStorage', $(".login").length > 0
$(".login").length > 0
+ createTextElement = (pageElement, beforeElement) ->
+ item =
+ type: 'paragraph'
+ id: util.randomBytes(8)
+ itemElement = $ """
+ <div class="item paragraph" data-id=#{item.id}></div>
+ """
+ itemElement
+ .data('item', item)
+ .data('pageElement', pageElement)
+ beforeElement.after itemElement
+ plugin.do itemElement, item
+ itemBefore = wiki.getItem beforeElement
+ wiki.textEditor itemElement, item
+ sleep = (time, code) -> setTimeout code, time
+ sleep 500, -> pageHandler.put pageElement, {item: item, id: item.id, type: 'add', after: itemBefore?.id}
+
textEditor = wiki.textEditor = (div, item) ->
textarea = $("<textarea>#{original = item.text ? ''}</textarea>")
.focusout ->
@@ -116,6 +133,12 @@ $ ->
if (e.altKey || e.ctlKey || e.metaKey) and e.which == 83 #alt-s
textarea.focusout()
return false
+ #NH
+ if e.which == $.ui.keyCode.ENTER
+ textarea.focusout()
+ pageElement = div.parent().parent()
+ createTextElement(pageElement, div)
+ return false
.bind 'dblclick', (e) ->
return false; #don't pass dblclick on to the div, as it'll reload
View
23 client/lib/refresh.coffee
@@ -38,16 +38,19 @@ initDragging = (pageElement) ->
initAddButton = (pageElement) ->
pageElement.find(".add-factory").live "click", (evt) ->
evt.preventDefault()
- item =
- type: "factory"
- id: util.randomBytes(8)
- itemElement = $("<div />", class: "item factory").data('item',item).attr('data-id', item.id)
- itemElement.data 'pageElement', pageElement
- pageElement.find(".story").append(itemElement)
- plugin.do itemElement, item
- beforeElement = itemElement.prev('.item')
- before = wiki.getItem(beforeElement)
- pageHandler.put pageElement, {item: item, id: item.id, type: "add", after: before?.id}
+ createFactory(pageElement)
+
+createFactory = (pageElement) ->
+ item =
+ type: "factory"
+ id: util.randomBytes(8)
+ itemElement = $("<div />", class: "item factory").data('item',item).attr('data-id', item.id)
+ itemElement.data 'pageElement', pageElement
+ pageElement.find(".story").append(itemElement)
+ plugin.do itemElement, item
+ beforeElement = itemElement.prev('.item')
+ before = wiki.getItem(beforeElement)
+ pageHandler.put pageElement, {item: item, id: item.id, type: "add", after: before?.id}
emitHeader = (pageElement, page) ->
site = $(pageElement).data('site')
View
44 client/test/testclient.js
@@ -1027,7 +1027,7 @@ require.define("/test/refresh.coffee", function (require, module, exports, __dir
require.define("/lib/refresh.coffee", function (require, module, exports, __dirname, __filename) {
(function() {
- var emitHeader, handleDragging, initAddButton, initDragging, pageHandler, plugin, refresh, state, util;
+ var createFactory, emitHeader, handleDragging, initAddButton, initDragging, pageHandler, plugin, refresh, state, util;
util = require('./util.coffee');
@@ -1078,26 +1078,30 @@ require.define("/lib/refresh.coffee", function (require, module, exports, __dirn
initAddButton = function(pageElement) {
return pageElement.find(".add-factory").live("click", function(evt) {
- var before, beforeElement, item, itemElement;
evt.preventDefault();
- item = {
- type: "factory",
- id: util.randomBytes(8)
- };
- itemElement = $("<div />", {
- "class": "item factory"
- }).data('item', item).attr('data-id', item.id);
- itemElement.data('pageElement', pageElement);
- pageElement.find(".story").append(itemElement);
- plugin["do"](itemElement, item);
- beforeElement = itemElement.prev('.item');
- before = wiki.getItem(beforeElement);
- return pageHandler.put(pageElement, {
- item: item,
- id: item.id,
- type: "add",
- after: before != null ? before.id : void 0
- });
+ return createFactory(pageElement);
+ });
+ };
+
+ createFactory = function(pageElement) {
+ var before, beforeElement, item, itemElement;
+ item = {
+ type: "factory",
+ id: util.randomBytes(8)
+ };
+ itemElement = $("<div />", {
+ "class": "item factory"
+ }).data('item', item).attr('data-id', item.id);
+ itemElement.data('pageElement', pageElement);
+ pageElement.find(".story").append(itemElement);
+ plugin["do"](itemElement, item);
+ beforeElement = itemElement.prev('.item');
+ before = wiki.getItem(beforeElement);
+ return pageHandler.put(pageElement, {
+ item: item,
+ id: item.id,
+ type: "add",
+ after: before != null ? before.id : void 0
});
};
View
5 client/theme/granite.css
@@ -25,7 +25,12 @@ body {
float: left;
width: 26px; }
+.control-buttons {
+ right: 3px;
+}
+
.button {
+ left: 0;
color: #c14615;
font-size: 21px;
padding: 0.1em;
View
2  server/express/views/static.html
@@ -6,7 +6,7 @@
<meta content='width=device-width, height=device-height, initial-scale=1.0, user-scalable=no' name='viewport'>
<link id='favicon' href='/favicon.png' rel='icon' type='image/png'>
<link href='/style.css' rel='stylesheet' type='text/css'>
- <!-- <link href='/theme/granite.css' rel='stylesheet' type='text/css'> -->
+ <link href='/theme/granite.css' rel='stylesheet' type='text/css'>
<script src='/js/jquery-1.7.1.min.js' type='text/javascript'></script>
<script src='/js/jquery-ui-1.8.16.custom.min.js' type='text/javascript'></script>
<link href='/js/jquery-ui-1.8.16.custom.css' rel='stylesheet' type='text/css'>
Something went wrong with that request. Please try again.