Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
Choose a Base Repository
hypothesis/h
robertknight/h
40a/h
AFDudley/h
BigBlueHat/h
BinaryStars/h
CCH543/h
Cinemacloud/h
Ericgood/h
FTG-003/h
Forethinker/h
GratefulTony/h
HGldJ1966/h
JJediny/h
John-Williams/h
Laurian/h
LittleFancy/h
MattyQ/h
Mishkin2015/h
RichardLitt/h
Staffan1/h
SteelWagstaff/h
TowerBR/h
VanyTang/h
abigailricarte/h
ackermann/h
alecchap/h
alesarrett/h
alexsegura/h
almereyda/h
alon/h
andzi/h
angelicxsoul/h
ansmoh/h
apurvajalit/h
arjunvasan/h
asdevor/h
bZichett/h
badgettrg/Webmarks
balmas/h
balupton/h
bbarker/h
bennlich/h
benthor/h
blakewest/h
bogste/h
bradparks/h
brittanystoroz/h
buiquangchien/h
cdchapman/h
charblanc/h
chowsamihq/h
chr7stos/Webmarks
chrber/h
chrismPssina/h
christinaphamAD/h
cmbirk/h
codeaudit/h
coolcool21/h
cove/h
csillag/h
danjimilk/h
dannyhope/h
daredream/h
davidmcclure/h
dennisplucinik/h
dezynetechnologies/h
diegodlh/h
djcun95/h
donsequitur/h
edsu/h
eiro10/h
emckean/h
ercchy/h
eshellman/h
fangang123/h
fchasen/h
fcrimins/h
fhirsch/h
ficolo/h
fragkopoulos/h
gauravkeerthi/h
geass/h
gergely-ujvari/h
gitter-badger/h
gnott/h
gobengo/h
gorinovic/h
gus3000/h
hashin/h
helemaalbigt/h
hmstepanek/h
hwasiti/h
hylhero/h
hyperstudio/h
iHDeveloper/h
imeysam/h
jackspaceBerkeley/h
jarey/h
jasdeep/h
jason790/h
jasonzou/j
jazahn/h
jccr/h
jean/h
jeka57/h
jeremydean/h
jermnelson/h
jibe-b/h
jnishiyama/h
jojksd/h
jpadilla/h
jtremback/h
judell/h
juli-so/h
kabacs/h
karissa/h
kaushikvijay/h
kaydoh/h
kill4uk/h
klopiinas/h
klrkdekira/h
koulihong311/h
krassif/h
krstnkngs/h
leoqmp/h
linhua55/h
lucadealfaro/h
lyspooner/h
lyzadanger/h
m1yag1/h
magee/h
mambocab/h
manunymous/h
maraino/h
mari-ja/h
markbarratt/h
martinq/h
mbbaig/h
mcarv63/h
meawoppl/h
meflyup/h
metasj/h
mgasner/h
mgax/h
mollycr/h
mrchrisadams/h
mrienstra/h
mshavlovsky/h
muddasani/h
nagyist/hyphothesis-h
nagyistoce/hypothesis-h
nanxio/h
neozhangthe1/h
ningyifan/h
nkingsley/h
nlholdem/h
nlisgo/h
noscripter/h
nshkuro/h
odnodn/h
oliversauter/h
openbizgit/h
opengovfoundation/h
openstax/hypothesis-server
ouroboros8/h
pablomarti/h
pamo/h
philipn/h
philschatz/h
pinballwonder/h
plainspace/h
raowl/h
rickyhan/h
rmoorman/h
rmtsukuru/h
rowhit/h
rsarxiv/h
saakaifoundry/h
samrose/h
scharf/h
shepazu/h
sherah/h
shofheinz/h
soapdog/h
ssin122/test-h
st-fresh/h
stuk88/h
sylvanmist/h
tetratorus/h
tilgovi/h
tomnar/h
trivenews/h
truthadjustr/h
utngz/h
voidfiles/h
wenchen/h
yargevad/h
yumatch/h
zshen777/h
Nothing to show
Choose a base branch
2263-tooltip-for-note-button
anchoring-rewrite
angular-1.3
angular-1.4_rob
app-cors
app_startup_metrics
assets-view-tests
atom-link-related
autoprefix_css
avoid-group-reload-on-change
better-dockerfile-caching
bridge_timeout_debugging
browserify_extension
build_deps_before_test
categorized_logging
change-username-form
check-asset-versions-v2
decaf
deduplicate-password-validation
deps-auto-update
ejp
embedding-docs-link
enable-group-creator-deletion-split-delete-method
enriched-stream
extension-badge-tint
extension_badge_refactor
extension_build_type_indicator
extension_embed_conflict
form-preact
frontend_build_refactor
frontend_tests_docs
gh2505-autolink
gh2553-fix_selection_filter
gh2561-highlight_text_revert
gh2563-test_timeout_debugging
gh2568-safari_missing_urls
gh2641-no_reload_on_groups_list_change
gh2642-reload_fouc
gh2646-bucket_bar_pos
gh2654-safari_search_expander
gh2663-do_not_switch_group_on_leave
groups_changed_exception
groups_list_selection_fix
groups
icon_font_fix
icon_font_fix_2
improve-form-field-list-design
index-and-search-elasticsearch6
index-authority
karma-watch-target
login-with-google
master
multitarget
nipsa
oauth-admin
oauth-login-prototype
p-frontend_build_refactor
p-fx-webextensions
p-new_highlighter
p-new_threading_impl
p-preact_annotation
p-preact_components
p-thread_model
parallel-reindex
publish-btn-cleanup
py3-docker
quote-anchor-integration-test
refactor-sidebar-injector-test
replace-angular_websocket
reply-count-badge
rob-setup_docs_corrections
search-bar-preact
search-bar-tidy-up
search-decaf
search-tests
server_sorted_groups
support-sqs
t87-editorconfig-sass
t87-group_scope_dropdown_ui
t88-remove_self_from_group
t89-combined_scope_save_btn
t89-group_list_css_refactor
t90-search_icon_click
t90-top_bar_new_design
t90-top_bar_refactor
t91-sort_dropdown_move_to_top_bar
t91-sort_dropdown_refactor
t93-clear_selection_btn_ux
t93-create_group_refresh
t105-group_push_notifications
t112-center_post_dropdown
t125-post_button_ui_fixes
t139-new_loading_indicator
t148-card_group_style_refactor
t152-move_unsaved_annot_to_current_group
t182-chrome_perms_refactor
t187-new_homepage_design
thread-collapsing
travis-flake8
v0.2.x
v0.3.x
visual-truncation
websocket-send-on-reconnect
z-login-with-google
z-py3-working-docker-img
z-py3
z-search-tests
Nothing to show
Choose a Head Repository
hypothesis/h
robertknight/h
40a/h
AFDudley/h
BigBlueHat/h
BinaryStars/h
CCH543/h
Cinemacloud/h
Ericgood/h
FTG-003/h
Forethinker/h
GratefulTony/h
HGldJ1966/h
JJediny/h
John-Williams/h
Laurian/h
LittleFancy/h
MattyQ/h
Mishkin2015/h
RichardLitt/h
Staffan1/h
SteelWagstaff/h
TowerBR/h
VanyTang/h
abigailricarte/h
ackermann/h
alecchap/h
alesarrett/h
alexsegura/h
almereyda/h
alon/h
andzi/h
angelicxsoul/h
ansmoh/h
apurvajalit/h
arjunvasan/h
asdevor/h
bZichett/h
badgettrg/Webmarks
balmas/h
balupton/h
bbarker/h
bennlich/h
benthor/h
blakewest/h
bogste/h
bradparks/h
brittanystoroz/h
buiquangchien/h
cdchapman/h
charblanc/h
chowsamihq/h
chr7stos/Webmarks
chrber/h
chrismPssina/h
christinaphamAD/h
cmbirk/h
codeaudit/h
coolcool21/h
cove/h
csillag/h
danjimilk/h
dannyhope/h
daredream/h
davidmcclure/h
dennisplucinik/h
dezynetechnologies/h
diegodlh/h
djcun95/h
donsequitur/h
edsu/h
eiro10/h
emckean/h
ercchy/h
eshellman/h
fangang123/h
fchasen/h
fcrimins/h
fhirsch/h
ficolo/h
fragkopoulos/h
gauravkeerthi/h
geass/h
gergely-ujvari/h
gitter-badger/h
gnott/h
gobengo/h
gorinovic/h
gus3000/h
hashin/h
helemaalbigt/h
hmstepanek/h
hwasiti/h
hylhero/h
hyperstudio/h
iHDeveloper/h
imeysam/h
jackspaceBerkeley/h
jarey/h
jasdeep/h
jason790/h
jasonzou/j
jazahn/h
jccr/h
jean/h
jeka57/h
jeremydean/h
jermnelson/h
jibe-b/h
jnishiyama/h
jojksd/h
jpadilla/h
jtremback/h
judell/h
juli-so/h
kabacs/h
karissa/h
kaushikvijay/h
kaydoh/h
kill4uk/h
klopiinas/h
klrkdekira/h
koulihong311/h
krassif/h
krstnkngs/h
leoqmp/h
linhua55/h
lucadealfaro/h
lyspooner/h
lyzadanger/h
m1yag1/h
magee/h
mambocab/h
manunymous/h
maraino/h
mari-ja/h
markbarratt/h
martinq/h
mbbaig/h
mcarv63/h
meawoppl/h
meflyup/h
metasj/h
mgasner/h
mgax/h
mollycr/h
mrchrisadams/h
mrienstra/h
mshavlovsky/h
muddasani/h
nagyist/hyphothesis-h
nagyistoce/hypothesis-h
nanxio/h
neozhangthe1/h
ningyifan/h
nkingsley/h
nlholdem/h
nlisgo/h
noscripter/h
nshkuro/h
odnodn/h
oliversauter/h
openbizgit/h
opengovfoundation/h
openstax/hypothesis-server
ouroboros8/h
pablomarti/h
pamo/h
philipn/h
philschatz/h
pinballwonder/h
plainspace/h
raowl/h
rickyhan/h
rmoorman/h
rmtsukuru/h
rowhit/h
rsarxiv/h
saakaifoundry/h
samrose/h
scharf/h
shepazu/h
sherah/h
shofheinz/h
soapdog/h
ssin122/test-h
st-fresh/h
stuk88/h
sylvanmist/h
tetratorus/h
tilgovi/h
tomnar/h
trivenews/h
truthadjustr/h
utngz/h
voidfiles/h
wenchen/h
yargevad/h
yumatch/h
zshen777/h
Nothing to show
Choose a head branch
2263-tooltip-for-note-button
anchoring-rewrite
angular-1.3
angular-1.4_rob
app-cors
app_startup_metrics
assets-view-tests
atom-link-related
autoprefix_css
avoid-group-reload-on-change
better-dockerfile-caching
bridge_timeout_debugging
browserify_extension
build_deps_before_test
categorized_logging
change-username-form
check-asset-versions-v2
decaf
deduplicate-password-validation
deps-auto-update
ejp
embedding-docs-link
enable-group-creator-deletion-split-delete-method
enriched-stream
extension-badge-tint
extension_badge_refactor
extension_build_type_indicator
extension_embed_conflict
form-preact
frontend_build_refactor
frontend_tests_docs
gh2505-autolink
gh2553-fix_selection_filter
gh2561-highlight_text_revert
gh2563-test_timeout_debugging
gh2568-safari_missing_urls
gh2641-no_reload_on_groups_list_change
gh2642-reload_fouc
gh2646-bucket_bar_pos
gh2654-safari_search_expander
gh2663-do_not_switch_group_on_leave
groups_changed_exception
groups_list_selection_fix
groups
icon_font_fix
icon_font_fix_2
improve-form-field-list-design
index-and-search-elasticsearch6
index-authority
karma-watch-target
login-with-google
master
multitarget
nipsa
oauth-admin
oauth-login-prototype
p-frontend_build_refactor
p-fx-webextensions
p-new_highlighter
p-new_threading_impl
p-preact_annotation
p-preact_components
p-thread_model
parallel-reindex
publish-btn-cleanup
py3-docker
quote-anchor-integration-test
refactor-sidebar-injector-test
replace-angular_websocket
reply-count-badge
rob-setup_docs_corrections
search-bar-preact
search-bar-tidy-up
search-decaf
search-tests
server_sorted_groups
support-sqs
t87-editorconfig-sass
t87-group_scope_dropdown_ui
t88-remove_self_from_group
t89-combined_scope_save_btn
t89-group_list_css_refactor
t90-search_icon_click
t90-top_bar_new_design
t90-top_bar_refactor
t91-sort_dropdown_move_to_top_bar
t91-sort_dropdown_refactor
t93-clear_selection_btn_ux
t93-create_group_refresh
t105-group_push_notifications
t112-center_post_dropdown
t125-post_button_ui_fixes
t139-new_loading_indicator
t148-card_group_style_refactor
t152-move_unsaved_annot_to_current_group
t182-chrome_perms_refactor
t187-new_homepage_design
thread-collapsing
travis-flake8
v0.2.x
v0.3.x
visual-truncation
websocket-send-on-reconnect
z-login-with-google
z-py3-working-docker-img
z-py3
z-search-tests
Nothing to show
  • 1 commit
  • 9 files changed
  • 0 commit comments
  • 1 contributor
Commits on Oct 29, 2015
Preserve unsaved changes when switching groups
Refactor the drafts service to preserve unsaved edits
when switching groups.

This rewrites the drafts service so that it can preserve
unsaved changes for new and existing annotations when
switching groups.

For each annotation, the drafts service now maintains
an object containing the unsaved changes in addition to
the model for the annotation which was being edited.

 * For new annotations, the annotation is moved to the current
   group when switching groups.

 * For edits to existing annotations, the unsaved changes are
   saved to the drafts service.

 * When an annotation card is created, switch to editing mode
   automatically if a draft is present.

 * Avoid automatically discarding the draft when an annotation
   is unloaded. This allows unsaved edits to existing annotations
   in a group to be restored when switching back to the group.
@@ -20,7 +20,6 @@ function getContainer(threading, annotation) {
// @ngInject
function annotationMapper($rootScope, threading, store) {
function loadAnnotations(annotations) {
console.log('loading %d annots', annotations.length);
var loaded = [];
annotations.forEach(function (annotation) {
@@ -38,7 +37,6 @@ function annotationMapper($rootScope, threading, store) {
}
function unloadAnnotations(annotations) {
console.log('unloading %d annots', annotations.length);
annotations.forEach(function (annotation) {
var container = getContainer(threading, annotation);
if (container !== null && annotation !== container.message) {
@@ -25,8 +25,8 @@ resolve =
threading.createIdTable([])
threading.root = mail.messageContainer()
# Reload all unsaved annotations
threading.thread(drafts.all())
# Reload all new, unsaved annotations
threading.thread(drafts.unsaved())
return threading
]
@@ -1,5 +1,7 @@
### global -validate ###
events = require('../events');
STORAGE_KEY = 'hypothesis.privacy'
# Validate an annotation.
@@ -51,6 +53,7 @@ AnnotationController = [
drafts, flash, permissions, tags, time,
annotationUI, annotationMapper, session, groups, localStorage) ->
# @annotation is the view model, containing the unsaved annotation changes
@annotation = {}
@action = 'view'
@document = null
@@ -59,6 +62,8 @@ AnnotationController = [
@isSidebar = false
@timestamp = null
# 'model' is the domain model, containing the currently saved version
# of the annotation
model = $scope.annotationGet()
# Set the group of new annotations.
@@ -193,7 +198,8 @@ AnnotationController = [
# @description Switches the view to an editor.
###
this.edit = ->
drafts.add model, => this.revert()
if !drafts.get(model)
updateDraft(model)
@action = if model.id? then 'edit' else 'create'
@editing = true
@preview = 'no'
@@ -228,6 +234,16 @@ AnnotationController = [
domainModel, viewModel,
{tags: (tag.text for tag in viewModel.tags)})
# Create or update the existing draft for this annotation using
# the text and tags from the domain model in 'draft'
updateDraft = (draft) ->
# drafts only preserve the text and tags for the
# annotation, changes to other properties are not preserved
drafts.update(model, {
text: draft.text
tags: draft.tags
})
###*
# @ngdoc method
# @name annotation.AnnotationController#save
@@ -299,7 +315,13 @@ AnnotationController = [
this.render = ->
# Extend the view model with a copy of the domain model.
# Note that copy is used so that deep properties aren't shared.
@annotation = angular.extend {}, angular.copy model
@annotation = angular.extend {}, angular.copy(model)
# if we have unsaved changes to this annotation, apply them
# to the view model
draft = drafts.get(model)
if draft
angular.extend @annotation, angular.copy(draft)
# Set the URI
@annotationURI = new URL("/a/#{@annotation.id}", this.baseURI).href
@@ -333,7 +355,7 @@ AnnotationController = [
@document.title = @document.title[0..29] + ''
# Form the tags for ngTagsInput.
@annotation.tags = ({text} for text in (model.tags or []))
@annotation.tags = ({text} for text in (@annotation.tags or []))
updateTimestamp = (repeat=false) =>
@timestamp = time.toFuzzyString model.updated
@@ -348,10 +370,8 @@ AnnotationController = [
# Export the baseURI for the share link
this.baseURI = $document.prop('baseURI')
# Discard the draft if the scope goes away.
$scope.$on '$destroy', ->
updateTimestamp = angular.noop
drafts.remove model
# watch for changes to the domain model and recreate the view model
# when it changes
@@ -369,7 +389,7 @@ AnnotationController = [
$rootScope.$emit('annotationCreated', model)
highlight = false # Prevents double highlight creation.
else
drafts.add model, => this.revert()
updateDraft(model)
updateTimestamp(model is old) # repeat on first run
this.render()
@@ -381,8 +401,27 @@ AnnotationController = [
model.permissions ?= {}
model.user ?= userid
# Start editing brand new annotations immediately
unless model.id? or (this.isHighlight() and highlight) then this.edit()
# if this is a new annotation or we have unsaved changes,
# then start editing immediately
isNewAnnotation = !(model.id || (this.isHighlight() && highlight));
if isNewAnnotation || drafts.get(model)
this.edit()
# when the current group changes, persist any unsaved changes using
# the drafts service. They will be restored when this annotation is
# next loaded.
$scope.$on events.GROUP_FOCUSED, ->
if !vm.editing
return
draftDomainModel = {}
updateDomainModel(draftDomainModel, vm.annotation)
updateDraft(draftDomainModel)
# move any new annotations to the currently focused group when
# switching groups. See GH #2689 for context
if !model.id
model.group = groups.focused().id
this
]
@@ -56,8 +56,9 @@ describe 'annotation', ->
deleteAnnotation: sandbox.stub()
fakeAnnotationUI = {}
fakeDrafts = {
add: sandbox.stub()
update: sandbox.stub()
remove: sandbox.stub()
get: sandbox.stub()
}
fakeFeatures = {
flagEnabled: sandbox.stub().returns(true)
@@ -555,8 +556,9 @@ describe("AnnotationController", ->
momentFilter: momentFilter or {}
urlencodeFilter: urlencodeFilter or {}
drafts: drafts or {
add: ->
update: ->
remove: ->
get: ->
}
features: features or {
flagEnabled: -> true

This file was deleted.

Oops, something went wrong.
View
@@ -0,0 +1,84 @@
/**
* The drafts service provides temporary storage for unsaved edits
* to new or existing annotations.
*
* A draft consists of a 'model' which is the original annotation
* which the draft is associated with and `changes' which is
* a set of edits to the original annotation.
*/
function DraftStore() {
this._drafts = [];
// returns true if 'draft' is a draft for a given
// annotation. Annotations are matched by ID
// and annotation instance (for unsaved annotations
// which have no ID)
function match(draft, model) {
return draft.model === model ||
(draft.model.id && model.id === draft.model.id);
}
/**
* Returns a list of all new annotations (those with no ID) for which
* unsaved drafts exist.
*/
this.unsaved = function unsaved() {
return this._drafts.filter(function (draft) {
return !draft.model.id;
}).map(function (draft) {
return draft.model;
});
}
/** Retrieve the draft changes for an annotation. */
this.get = function get(model) {
for (var i=0; i < this._drafts.length; i++) {
if (match(this._drafts[i], model)) {
return this._drafts[i].changes;
}
}
}
/**
* Update the draft version for a given annotation, replacing any
* existing draft.
*/
this.update = function update(model, changes) {
var newDraft = {
model: model,
changes: changes,
};
this.remove(model);
this._drafts.push(newDraft);
}
/** Remove the draft version of an annotation. */
this.remove = function remove(model) {
this._drafts = this._drafts.filter(function (draft) {
return !match(draft, model);
});
}
/** Prompt to discard any unsaved drafts. */
this.discard = function discard() {
// TODO - Replace this with a UI which doesn't look terrible
var text;
if (this._drafts.length === 1) {
text = 'You have an unsaved reply.\n\n' +
'Do you really want to discard this draft?';
} else if (this._drafts.length > 1) {
text = 'You have ' + this._drafts.length + ' unsaved replies.\n\n'
'Do you really want to discard these drafts?';
}
if (this._drafts.length === 0 || window.confirm(text)) {
this._drafts = [];
return true;
} else {
return false;
}
}
}
module.exports = function () {
return new DraftStore();
};
@@ -145,12 +145,6 @@ describe 'AppController', ->
createController()
assert.isFalse($scope.shareDialog.visible)
it 'reloads the view when the focused group changes', ->
createController()
fakeRoute.reload = sinon.spy()
$scope.$broadcast(events.GROUP_FOCUSED)
assert.calledOnce(fakeRoute.reload)
it 'does not reload the view when the logged-in user changes on first load', ->
createController()
fakeRoute.reload = sinon.spy()
@@ -63,9 +63,14 @@ describe 'WidgetController', ->
focused: -> {id: 'foo'}
}
fakeDrafts = {
unsaved: []
}
$provide.value 'annotationMapper', fakeAnnotationMapper
$provide.value 'annotationUI', fakeAnnotationUI
$provide.value 'crossframe', fakeCrossFrame
$provide.value 'drafts', fakeDrafts
$provide.value 'store', fakeStore
$provide.value 'streamer', fakeStreamer
$provide.value 'streamFilter', fakeStreamFilter
@@ -19,10 +19,10 @@ module.exports = class WidgetController
loaded = []
_resetAnnotations = ->
# Unload all the annotations
# Unload all annotations
annotationMapper.unloadAnnotations(threading.annotationList())
# Reload all the drafts
threading.thread(drafts.all())
# Reload all new unsaved annotations
threading.thread(drafts.unsaved())
_loadAnnotationsFrom = (query, offset) =>
queryCore =

No commit comments for this range