Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
11ab71ea
Unverified
Commit
11ab71ea
authored
Jul 17, 2019
by
Kyle Keating
Committed by
GitHub
Jul 17, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1238 from hypothesis/draft-service-refactor
Convert draft service to store
parents
b5d16912
f98a4f51
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
483 additions
and
318 deletions
+483
-318
annotation.js
src/sidebar/components/annotation.js
+11
-12
hypothesis-app.js
src/sidebar/components/hypothesis-app.js
+9
-8
sidebar-content.js
src/sidebar/components/sidebar-content.js
+0
-1
annotation-test.js
src/sidebar/components/test/annotation-test.js
+27
-28
hypothesis-app-test.js
src/sidebar/components/test/hypothesis-app-test.js
+12
-19
index.js
src/sidebar/index.js
+0
-1
drafts.js
src/sidebar/services/drafts.js
+0
-124
root-thread.js
src/sidebar/services/root-thread.js
+3
-3
drafts-test.js
src/sidebar/services/test/drafts-test.js
+0
-108
root-thread-test.js
src/sidebar/services/test/root-thread-test.js
+8
-13
index.js
src/sidebar/store/index.js
+2
-0
drafts.js
src/sidebar/store/modules/drafts.js
+183
-0
drafts-test.js
src/sidebar/store/modules/test/drafts-test.js
+228
-0
threading-test.js
src/sidebar/test/integration/threading-test.js
+0
-1
No files found.
src/sidebar/components/annotation.js
View file @
11ab71ea
...
@@ -54,7 +54,6 @@ function AnnotationController(
...
@@ -54,7 +54,6 @@ function AnnotationController(
annotationMapper
,
annotationMapper
,
api
,
api
,
bridge
,
bridge
,
drafts
,
flash
,
flash
,
groups
,
groups
,
permissions
,
permissions
,
...
@@ -177,7 +176,7 @@ function AnnotationController(
...
@@ -177,7 +176,7 @@ function AnnotationController(
// created by the annotate button) or it has edits not yet saved to the
// created by the annotate button) or it has edits not yet saved to the
// server - then open the editor on AnnotationController instantiation.
// server - then open the editor on AnnotationController instantiation.
if
(
!
newlyCreatedByHighlightButton
)
{
if
(
!
newlyCreatedByHighlightButton
)
{
if
(
isNew
(
self
.
annotation
)
||
drafts
.
ge
t
(
self
.
annotation
))
{
if
(
isNew
(
self
.
annotation
)
||
store
.
getDraf
t
(
self
.
annotation
))
{
self
.
edit
();
self
.
edit
();
}
}
}
}
...
@@ -218,7 +217,7 @@ function AnnotationController(
...
@@ -218,7 +217,7 @@ function AnnotationController(
});
});
}
else
{
}
else
{
// User isn't logged in, save to drafts.
// User isn't logged in, save to drafts.
drafts
.
update
(
self
.
annotation
,
self
.
state
());
store
.
createDraft
(
self
.
annotation
,
self
.
state
());
}
}
}
}
...
@@ -293,8 +292,8 @@ function AnnotationController(
...
@@ -293,8 +292,8 @@ function AnnotationController(
* @description Switches the view to an editor.
* @description Switches the view to an editor.
*/
*/
this
.
edit
=
function
()
{
this
.
edit
=
function
()
{
if
(
!
drafts
.
ge
t
(
self
.
annotation
))
{
if
(
!
store
.
getDraf
t
(
self
.
annotation
))
{
drafts
.
update
(
self
.
annotation
,
self
.
state
());
store
.
createDraft
(
self
.
annotation
,
self
.
state
());
}
}
};
};
...
@@ -305,7 +304,7 @@ function AnnotationController(
...
@@ -305,7 +304,7 @@ function AnnotationController(
* (i.e. the annotation editor form should be open), `false` otherwise.
* (i.e. the annotation editor form should be open), `false` otherwise.
*/
*/
this
.
editing
=
function
()
{
this
.
editing
=
function
()
{
return
drafts
.
ge
t
(
self
.
annotation
)
&&
!
self
.
isSaving
;
return
store
.
getDraf
t
(
self
.
annotation
)
&&
!
self
.
isSaving
;
};
};
/**
/**
...
@@ -431,7 +430,7 @@ function AnnotationController(
...
@@ -431,7 +430,7 @@ function AnnotationController(
* @description Reverts an edit in progress and returns to the viewer.
* @description Reverts an edit in progress and returns to the viewer.
*/
*/
this
.
revert
=
function
()
{
this
.
revert
=
function
()
{
drafts
.
remove
(
self
.
annotation
);
store
.
removeDraft
(
self
.
annotation
);
if
(
isNew
(
self
.
annotation
))
{
if
(
isNew
(
self
.
annotation
))
{
$rootScope
.
$broadcast
(
events
.
ANNOTATION_DELETED
,
self
.
annotation
);
$rootScope
.
$broadcast
(
events
.
ANNOTATION_DELETED
,
self
.
annotation
);
}
}
...
@@ -466,7 +465,7 @@ function AnnotationController(
...
@@ -466,7 +465,7 @@ function AnnotationController(
const
event
=
isNew
(
self
.
annotation
)
const
event
=
isNew
(
self
.
annotation
)
?
events
.
ANNOTATION_CREATED
?
events
.
ANNOTATION_CREATED
:
events
.
ANNOTATION_UPDATED
;
:
events
.
ANNOTATION_UPDATED
;
drafts
.
remove
(
self
.
annotation
);
store
.
removeDraft
(
self
.
annotation
);
$rootScope
.
$broadcast
(
event
,
updatedModel
);
$rootScope
.
$broadcast
(
event
,
updatedModel
);
})
})
...
@@ -496,7 +495,7 @@ function AnnotationController(
...
@@ -496,7 +495,7 @@ function AnnotationController(
if
(
!
isReply
(
self
.
annotation
))
{
if
(
!
isReply
(
self
.
annotation
))
{
permissions
.
setDefault
(
privacy
);
permissions
.
setDefault
(
privacy
);
}
}
drafts
.
update
(
self
.
annotation
,
{
store
.
createDraft
(
self
.
annotation
,
{
tags
:
self
.
state
().
tags
,
tags
:
self
.
state
().
tags
,
text
:
self
.
state
().
text
,
text
:
self
.
state
().
text
,
isPrivate
:
privacy
===
'private'
,
isPrivate
:
privacy
===
'private'
,
...
@@ -571,7 +570,7 @@ function AnnotationController(
...
@@ -571,7 +570,7 @@ function AnnotationController(
};
};
this
.
setText
=
function
(
text
)
{
this
.
setText
=
function
(
text
)
{
drafts
.
update
(
self
.
annotation
,
{
store
.
createDraft
(
self
.
annotation
,
{
isPrivate
:
self
.
state
().
isPrivate
,
isPrivate
:
self
.
state
().
isPrivate
,
tags
:
self
.
state
().
tags
,
tags
:
self
.
state
().
tags
,
text
:
text
,
text
:
text
,
...
@@ -579,7 +578,7 @@ function AnnotationController(
...
@@ -579,7 +578,7 @@ function AnnotationController(
};
};
this
.
setTags
=
function
(
tags
)
{
this
.
setTags
=
function
(
tags
)
{
drafts
.
update
(
self
.
annotation
,
{
store
.
createDraft
(
self
.
annotation
,
{
isPrivate
:
self
.
state
().
isPrivate
,
isPrivate
:
self
.
state
().
isPrivate
,
tags
:
tags
,
tags
:
tags
,
text
:
self
.
state
().
text
,
text
:
self
.
state
().
text
,
...
@@ -587,7 +586,7 @@ function AnnotationController(
...
@@ -587,7 +586,7 @@ function AnnotationController(
};
};
this
.
state
=
function
()
{
this
.
state
=
function
()
{
const
draft
=
drafts
.
ge
t
(
self
.
annotation
);
const
draft
=
store
.
getDraf
t
(
self
.
annotation
);
if
(
draft
)
{
if
(
draft
)
{
return
draft
;
return
draft
;
}
}
...
...
src/sidebar/components/hypothesis-app.js
View file @
11ab71ea
...
@@ -43,7 +43,6 @@ function HypothesisAppController(
...
@@ -43,7 +43,6 @@ function HypothesisAppController(
store
,
store
,
auth
,
auth
,
bridge
,
bridge
,
drafts
,
features
,
features
,
flash
,
flash
,
frameSync
,
frameSync
,
...
@@ -150,18 +149,19 @@ function HypothesisAppController(
...
@@ -150,18 +149,19 @@ function HypothesisAppController(
const
promptToLogout
=
function
()
{
const
promptToLogout
=
function
()
{
// TODO - Replace this with a UI which doesn't look terrible.
// TODO - Replace this with a UI which doesn't look terrible.
let
text
=
''
;
let
text
=
''
;
if
(
drafts
.
count
()
===
1
)
{
const
drafts
=
store
.
countDrafts
();
if
(
drafts
===
1
)
{
text
=
text
=
'You have an unsaved annotation.
\
n'
+
'You have an unsaved annotation.
\
n'
+
'Do you really want to discard this draft?'
;
'Do you really want to discard this draft?'
;
}
else
if
(
drafts
.
count
()
>
1
)
{
}
else
if
(
drafts
>
1
)
{
text
=
text
=
'You have '
+
'You have '
+
drafts
.
count
()
+
drafts
+
' unsaved annotations.
\
n'
+
' unsaved annotations.
\
n'
+
'Do you really want to discard these drafts?'
;
'Do you really want to discard these drafts?'
;
}
}
return
drafts
.
count
()
===
0
||
$window
.
confirm
(
text
);
return
drafts
===
0
||
$window
.
confirm
(
text
);
};
};
// Log the user out.
// Log the user out.
...
@@ -169,11 +169,12 @@ function HypothesisAppController(
...
@@ -169,11 +169,12 @@ function HypothesisAppController(
if
(
!
promptToLogout
())
{
if
(
!
promptToLogout
())
{
return
;
return
;
}
}
store
.
clearGroups
();
store
.
clearGroups
();
drafts
.
unsaved
().
forEach
(
function
(
draft
)
{
store
.
unsavedAnnotations
().
forEach
(
function
(
annotation
)
{
$rootScope
.
$emit
(
events
.
ANNOTATION_DELETED
,
draft
);
$rootScope
.
$emit
(
events
.
ANNOTATION_DELETED
,
annotation
);
});
});
drafts
.
discard
();
store
.
discardAllDrafts
();
if
(
serviceConfig
(
settings
))
{
if
(
serviceConfig
(
settings
))
{
// Let the host page handle the signup request
// Let the host page handle the signup request
...
...
src/sidebar/components/sidebar-content.js
View file @
11ab71ea
...
@@ -26,7 +26,6 @@ function SidebarContentController(
...
@@ -26,7 +26,6 @@ function SidebarContentController(
store
,
store
,
annotationMapper
,
annotationMapper
,
api
,
api
,
drafts
,
features
,
features
,
frameSync
,
frameSync
,
groups
,
groups
,
...
...
src/sidebar/components/test/annotation-test.js
View file @
11ab71ea
...
@@ -107,7 +107,6 @@ describe('annotation', function() {
...
@@ -107,7 +107,6 @@ describe('annotation', function() {
let
fakeAnalytics
;
let
fakeAnalytics
;
let
fakeAnnotationMapper
;
let
fakeAnnotationMapper
;
let
fakeStore
;
let
fakeStore
;
let
fakeDrafts
;
let
fakeFlash
;
let
fakeFlash
;
let
fakeGroups
;
let
fakeGroups
;
let
fakePermissions
;
let
fakePermissions
;
...
@@ -137,7 +136,7 @@ describe('annotation', function() {
...
@@ -137,7 +136,7 @@ describe('annotation', function() {
// A new annotation won't have any saved drafts yet.
// A new annotation won't have any saved drafts yet.
if
(
!
annotation
.
id
)
{
if
(
!
annotation
.
id
)
{
fake
Drafts
.
ge
t
.
returns
(
null
);
fake
Store
.
getDraf
t
.
returns
(
null
);
}
}
return
{
return
{
...
@@ -190,12 +189,13 @@ describe('annotation', function() {
...
@@ -190,12 +189,13 @@ describe('annotation', function() {
fakeStore
=
{
fakeStore
=
{
updateFlagStatus
:
sandbox
.
stub
().
returns
(
true
),
updateFlagStatus
:
sandbox
.
stub
().
returns
(
true
),
};
// draft store
countDrafts
:
sandbox
.
stub
().
returns
(
0
),
fakeDrafts
=
{
createDraft
:
sandbox
.
stub
(),
update
:
sandbox
.
stub
(),
discardAllDrafts
:
sandbox
.
stub
(),
remove
:
sandbox
.
stub
(),
getDraft
:
sandbox
.
stub
().
returns
(
null
),
get
:
sandbox
.
stub
().
returns
(
null
),
getDraftIfNotEmpty
:
sandbox
.
stub
().
returns
(
null
),
removeDraft
:
sandbox
.
stub
(),
};
};
fakeFlash
=
{
fakeFlash
=
{
...
@@ -262,7 +262,6 @@ describe('annotation', function() {
...
@@ -262,7 +262,6 @@ describe('annotation', function() {
$provide
.
value
(
'store'
,
fakeStore
);
$provide
.
value
(
'store'
,
fakeStore
);
$provide
.
value
(
'api'
,
fakeApi
);
$provide
.
value
(
'api'
,
fakeApi
);
$provide
.
value
(
'bridge'
,
fakeBridge
);
$provide
.
value
(
'bridge'
,
fakeBridge
);
$provide
.
value
(
'drafts'
,
fakeDrafts
);
$provide
.
value
(
'flash'
,
fakeFlash
);
$provide
.
value
(
'flash'
,
fakeFlash
);
$provide
.
value
(
'groups'
,
fakeGroups
);
$provide
.
value
(
'groups'
,
fakeGroups
);
$provide
.
value
(
'permissions'
,
fakePermissions
);
$provide
.
value
(
'permissions'
,
fakePermissions
);
...
@@ -376,7 +375,7 @@ describe('annotation', function() {
...
@@ -376,7 +375,7 @@ describe('annotation', function() {
createDirective
(
annotation
);
createDirective
(
annotation
);
assert
.
notCalled
(
fakeApi
.
annotation
.
create
);
assert
.
notCalled
(
fakeApi
.
annotation
.
create
);
assert
.
called
(
fake
Drafts
.
update
);
assert
.
called
(
fake
Store
.
createDraft
);
});
});
it
(
'opens the sidebar when trying to save highlights while logged out'
,
()
=>
{
it
(
'opens the sidebar when trying to save highlights while logged out'
,
()
=>
{
...
@@ -418,7 +417,7 @@ describe('annotation', function() {
...
@@ -418,7 +417,7 @@ describe('annotation', function() {
it
(
'creates drafts for new annotations on initialization'
,
function
()
{
it
(
'creates drafts for new annotations on initialization'
,
function
()
{
const
annotation
=
fixtures
.
newAnnotation
();
const
annotation
=
fixtures
.
newAnnotation
();
createDirective
(
annotation
);
createDirective
(
annotation
);
assert
.
calledWith
(
fake
Drafts
.
update
,
annotation
,
{
assert
.
calledWith
(
fake
Store
.
createDraft
,
annotation
,
{
isPrivate
:
false
,
isPrivate
:
false
,
tags
:
annotation
.
tags
,
tags
:
annotation
.
tags
,
text
:
annotation
.
text
,
text
:
annotation
.
text
,
...
@@ -430,13 +429,13 @@ describe('annotation', function() {
...
@@ -430,13 +429,13 @@ describe('annotation', function() {
const
controller
=
createDirective
(
annotation
).
controller
;
const
controller
=
createDirective
(
annotation
).
controller
;
assert
.
notOk
(
controller
.
editing
());
assert
.
notOk
(
controller
.
editing
());
assert
.
notCalled
(
fake
Drafts
.
update
);
assert
.
notCalled
(
fake
Store
.
createDraft
);
});
});
it
(
'edits annotations with drafts on initialization'
,
function
()
{
it
(
'edits annotations with drafts on initialization'
,
function
()
{
const
annotation
=
fixtures
.
oldAnnotation
();
const
annotation
=
fixtures
.
oldAnnotation
();
// The drafts s
ervic
e has some draft changes for this annotation.
// The drafts s
tor
e has some draft changes for this annotation.
fake
Drafts
.
ge
t
.
returns
({
text
:
'foo'
,
tags
:
[]
});
fake
Store
.
getDraf
t
.
returns
({
text
:
'foo'
,
tags
:
[]
});
const
controller
=
createDirective
(
annotation
).
controller
;
const
controller
=
createDirective
(
annotation
).
controller
;
...
@@ -452,13 +451,13 @@ describe('annotation', function() {
...
@@ -452,13 +451,13 @@ describe('annotation', function() {
it
(
'returns true if the annotation has a draft'
,
function
()
{
it
(
'returns true if the annotation has a draft'
,
function
()
{
const
controller
=
createDirective
().
controller
;
const
controller
=
createDirective
().
controller
;
fake
Drafts
.
ge
t
.
returns
({
tags
:
[],
text
:
''
,
isPrivate
:
false
});
fake
Store
.
getDraf
t
.
returns
({
tags
:
[],
text
:
''
,
isPrivate
:
false
});
assert
.
isTrue
(
controller
.
editing
());
assert
.
isTrue
(
controller
.
editing
());
});
});
it
(
'returns false if the annotation has a draft but is being saved'
,
function
()
{
it
(
'returns false if the annotation has a draft but is being saved'
,
function
()
{
const
controller
=
createDirective
().
controller
;
const
controller
=
createDirective
().
controller
;
fake
Drafts
.
ge
t
.
returns
({
tags
:
[],
text
:
''
,
isPrivate
:
false
});
fake
Store
.
getDraf
t
.
returns
({
tags
:
[],
text
:
''
,
isPrivate
:
false
});
controller
.
isSaving
=
true
;
controller
.
isSaving
=
true
;
assert
.
isFalse
(
controller
.
editing
());
assert
.
isFalse
(
controller
.
editing
());
});
});
...
@@ -625,7 +624,7 @@ describe('annotation', function() {
...
@@ -625,7 +624,7 @@ describe('annotation', function() {
const
parts
=
createDirective
();
const
parts
=
createDirective
();
parts
.
controller
.
setPrivacy
(
'private'
);
parts
.
controller
.
setPrivacy
(
'private'
);
assert
.
calledWith
(
assert
.
calledWith
(
fake
Drafts
.
update
,
fake
Store
.
createDraft
,
parts
.
controller
.
annotation
,
parts
.
controller
.
annotation
,
sinon
.
match
({
sinon
.
match
({
isPrivate
:
true
,
isPrivate
:
true
,
...
@@ -637,7 +636,7 @@ describe('annotation', function() {
...
@@ -637,7 +636,7 @@ describe('annotation', function() {
const
parts
=
createDirective
();
const
parts
=
createDirective
();
parts
.
controller
.
setPrivacy
(
'shared'
);
parts
.
controller
.
setPrivacy
(
'shared'
);
assert
.
calledWith
(
assert
.
calledWith
(
fake
Drafts
.
update
,
fake
Store
.
createDraft
,
parts
.
controller
.
annotation
,
parts
.
controller
.
annotation
,
sinon
.
match
({
sinon
.
match
({
isPrivate
:
false
,
isPrivate
:
false
,
...
@@ -919,7 +918,7 @@ describe('annotation', function() {
...
@@ -919,7 +918,7 @@ describe('annotation', function() {
function
(
testCase
)
{
function
(
testCase
)
{
const
ann
=
fixtures
.
publicAnnotation
();
const
ann
=
fixtures
.
publicAnnotation
();
ann
.
group
=
testCase
.
group
.
id
;
ann
.
group
=
testCase
.
group
.
id
;
fake
Drafts
.
ge
t
.
returns
(
testCase
.
draft
);
fake
Store
.
getDraf
t
.
returns
(
testCase
.
draft
);
fakeGroups
.
get
.
returns
(
testCase
.
group
);
fakeGroups
.
get
.
returns
(
testCase
.
group
);
const
controller
=
createDirective
(
ann
).
controller
;
const
controller
=
createDirective
(
ann
).
controller
;
...
@@ -997,7 +996,7 @@ describe('annotation', function() {
...
@@ -997,7 +996,7 @@ describe('annotation', function() {
it
(
'removes the draft when saving an annotation succeeds'
,
function
()
{
it
(
'removes the draft when saving an annotation succeeds'
,
function
()
{
const
controller
=
createController
();
const
controller
=
createController
();
return
controller
.
save
().
then
(
function
()
{
return
controller
.
save
().
then
(
function
()
{
assert
.
calledWith
(
fake
Drafts
.
remove
,
annotation
);
assert
.
calledWith
(
fake
Store
.
removeDraft
,
annotation
);
});
});
});
});
...
@@ -1051,7 +1050,7 @@ describe('annotation', function() {
...
@@ -1051,7 +1050,7 @@ describe('annotation', function() {
.
stub
()
.
stub
()
.
returns
(
Promise
.
reject
({
status
:
-
1
}));
.
returns
(
Promise
.
reject
({
status
:
-
1
}));
return
controller
.
save
().
then
(
function
()
{
return
controller
.
save
().
then
(
function
()
{
assert
.
notCalled
(
fake
Drafts
.
remove
);
assert
.
notCalled
(
fake
Store
.
removeDraft
);
});
});
});
});
...
@@ -1069,7 +1068,7 @@ describe('annotation', function() {
...
@@ -1069,7 +1068,7 @@ describe('annotation', function() {
beforeEach
(
function
()
{
beforeEach
(
function
()
{
annotation
=
fixtures
.
defaultAnnotation
();
annotation
=
fixtures
.
defaultAnnotation
();
fake
Drafts
.
ge
t
.
returns
({
text
:
'unsaved change'
});
fake
Store
.
getDraf
t
.
returns
({
text
:
'unsaved change'
});
});
});
function
createController
()
{
function
createController
()
{
...
@@ -1099,7 +1098,7 @@ describe('annotation', function() {
...
@@ -1099,7 +1098,7 @@ describe('annotation', function() {
describe
(
'drafts'
,
function
()
{
describe
(
'drafts'
,
function
()
{
it
(
'starts editing immediately if there is a draft'
,
function
()
{
it
(
'starts editing immediately if there is a draft'
,
function
()
{
fake
Drafts
.
ge
t
.
returns
({
fake
Store
.
getDraf
t
.
returns
({
tags
:
[
'unsaved'
],
tags
:
[
'unsaved'
],
text
:
'unsaved-text'
,
text
:
'unsaved-text'
,
});
});
...
@@ -1108,7 +1107,7 @@ describe('annotation', function() {
...
@@ -1108,7 +1107,7 @@ describe('annotation', function() {
});
});
it
(
'uses the text and tags from the draft if present'
,
function
()
{
it
(
'uses the text and tags from the draft if present'
,
function
()
{
fake
Drafts
.
ge
t
.
returns
({
fake
Store
.
getDraf
t
.
returns
({
tags
:
[
'unsaved-tag'
],
tags
:
[
'unsaved-tag'
],
text
:
'unsaved-text'
,
text
:
'unsaved-text'
,
});
});
...
@@ -1121,15 +1120,15 @@ describe('annotation', function() {
...
@@ -1121,15 +1120,15 @@ describe('annotation', function() {
const
parts
=
createDirective
();
const
parts
=
createDirective
();
parts
.
controller
.
edit
();
parts
.
controller
.
edit
();
parts
.
controller
.
revert
();
parts
.
controller
.
revert
();
assert
.
calledWith
(
fake
Drafts
.
remove
,
parts
.
annotation
);
assert
.
calledWith
(
fake
Store
.
removeDraft
,
parts
.
annotation
);
});
});
it
(
'removes the draft when changes are saved'
,
function
()
{
it
(
'removes the draft when changes are saved'
,
function
()
{
const
annotation
=
fixtures
.
defaultAnnotation
();
const
annotation
=
fixtures
.
defaultAnnotation
();
const
controller
=
createDirective
(
annotation
).
controller
;
const
controller
=
createDirective
(
annotation
).
controller
;
fake
Drafts
.
ge
t
.
returns
({
text
:
'unsaved changes'
});
fake
Store
.
getDraf
t
.
returns
({
text
:
'unsaved changes'
});
return
controller
.
save
().
then
(
function
()
{
return
controller
.
save
().
then
(
function
()
{
assert
.
calledWith
(
fake
Drafts
.
remove
,
annotation
);
assert
.
calledWith
(
fake
Store
.
removeDraft
,
annotation
);
});
});
});
});
});
});
...
@@ -1140,7 +1139,7 @@ describe('annotation', function() {
...
@@ -1140,7 +1139,7 @@ describe('annotation', function() {
.
controller
;
.
controller
;
controller
.
edit
();
controller
.
edit
();
controller
.
revert
();
controller
.
revert
();
assert
.
calledWith
(
fake
Drafts
.
remove
,
controller
.
annotation
);
assert
.
calledWith
(
fake
Store
.
removeDraft
,
controller
.
annotation
);
});
});
it
(
'deletes the annotation if it was new'
,
function
()
{
it
(
'deletes the annotation if it was new'
,
function
()
{
...
...
src/sidebar/components/test/hypothesis-app-test.js
View file @
11ab71ea
...
@@ -15,7 +15,6 @@ describe('sidebar.components.hypothesis-app', function() {
...
@@ -15,7 +15,6 @@ describe('sidebar.components.hypothesis-app', function() {
let
fakeAnalytics
=
null
;
let
fakeAnalytics
=
null
;
let
fakeAuth
=
null
;
let
fakeAuth
=
null
;
let
fakeBridge
=
null
;
let
fakeBridge
=
null
;
let
fakeDrafts
=
null
;
let
fakeFeatures
=
null
;
let
fakeFeatures
=
null
;
let
fakeFlash
=
null
;
let
fakeFlash
=
null
;
let
fakeFrameSync
=
null
;
let
fakeFrameSync
=
null
;
...
@@ -64,6 +63,10 @@ describe('sidebar.components.hypothesis-app', function() {
...
@@ -64,6 +63,10 @@ describe('sidebar.components.hypothesis-app', function() {
clearSelectedAnnotations
:
sandbox
.
spy
(),
clearSelectedAnnotations
:
sandbox
.
spy
(),
getState
:
sinon
.
stub
(),
getState
:
sinon
.
stub
(),
clearGroups
:
sinon
.
stub
(),
clearGroups
:
sinon
.
stub
(),
// draft store
countDrafts
:
sandbox
.
stub
().
returns
(
0
),
discardAllDrafts
:
sandbox
.
stub
(),
unsavedAnnotations
:
sandbox
.
stub
().
returns
([]),
};
};
fakeAnalytics
=
{
fakeAnalytics
=
{
...
@@ -73,15 +76,6 @@ describe('sidebar.components.hypothesis-app', function() {
...
@@ -73,15 +76,6 @@ describe('sidebar.components.hypothesis-app', function() {
fakeAuth
=
{};
fakeAuth
=
{};
fakeDrafts
=
{
contains
:
sandbox
.
stub
(),
remove
:
sandbox
.
spy
(),
all
:
sandbox
.
stub
().
returns
([]),
discard
:
sandbox
.
spy
(),
count
:
sandbox
.
stub
().
returns
(
0
),
unsaved
:
sandbox
.
stub
().
returns
([]),
};
fakeFeatures
=
{
fakeFeatures
=
{
fetch
:
sandbox
.
spy
(),
fetch
:
sandbox
.
spy
(),
flagEnabled
:
sandbox
.
stub
().
returns
(
false
),
flagEnabled
:
sandbox
.
stub
().
returns
(
false
),
...
@@ -128,7 +122,6 @@ describe('sidebar.components.hypothesis-app', function() {
...
@@ -128,7 +122,6 @@ describe('sidebar.components.hypothesis-app', function() {
$provide
.
value
(
'store'
,
fakeStore
);
$provide
.
value
(
'store'
,
fakeStore
);
$provide
.
value
(
'auth'
,
fakeAuth
);
$provide
.
value
(
'auth'
,
fakeAuth
);
$provide
.
value
(
'analytics'
,
fakeAnalytics
);
$provide
.
value
(
'analytics'
,
fakeAnalytics
);
$provide
.
value
(
'drafts'
,
fakeDrafts
);
$provide
.
value
(
'features'
,
fakeFeatures
);
$provide
.
value
(
'features'
,
fakeFeatures
);
$provide
.
value
(
'flash'
,
fakeFlash
);
$provide
.
value
(
'flash'
,
fakeFlash
);
$provide
.
value
(
'frameSync'
,
fakeFrameSync
);
$provide
.
value
(
'frameSync'
,
fakeFrameSync
);
...
@@ -434,7 +427,7 @@ describe('sidebar.components.hypothesis-app', function() {
...
@@ -434,7 +427,7 @@ describe('sidebar.components.hypothesis-app', function() {
// Tests shared by both of the contexts below.
// Tests shared by both of the contexts below.
function
doSharedTests
()
{
function
doSharedTests
()
{
it
(
'prompts the user if there are drafts'
,
function
()
{
it
(
'prompts the user if there are drafts'
,
function
()
{
fake
Drafts
.
count
.
returns
(
1
);
fake
Store
.
countDrafts
.
returns
(
1
);
const
ctrl
=
createController
();
const
ctrl
=
createController
();
ctrl
.
logout
();
ctrl
.
logout
();
...
@@ -451,7 +444,7 @@ describe('sidebar.components.hypothesis-app', function() {
...
@@ -451,7 +444,7 @@ describe('sidebar.components.hypothesis-app', function() {
});
});
it
(
'emits "annotationDeleted" for each unsaved draft annotation'
,
function
()
{
it
(
'emits "annotationDeleted" for each unsaved draft annotation'
,
function
()
{
fake
Drafts
.
unsaved
=
sandbox
fake
Store
.
unsavedAnnotations
=
sandbox
.
stub
()
.
stub
()
.
returns
([
'draftOne'
,
'draftTwo'
,
'draftThree'
]);
.
returns
([
'draftOne'
,
'draftTwo'
,
'draftThree'
]);
const
ctrl
=
createController
();
const
ctrl
=
createController
();
...
@@ -479,12 +472,12 @@ describe('sidebar.components.hypothesis-app', function() {
...
@@ -479,12 +472,12 @@ describe('sidebar.components.hypothesis-app', function() {
ctrl
.
logout
();
ctrl
.
logout
();
assert
(
fake
Drafts
.
discard
.
calledOnce
);
assert
(
fake
Store
.
discardAllDrafts
.
calledOnce
);
});
});
it
(
'does not emit "annotationDeleted" if the user cancels the prompt'
,
function
()
{
it
(
'does not emit "annotationDeleted" if the user cancels the prompt'
,
function
()
{
const
ctrl
=
createController
();
const
ctrl
=
createController
();
fake
Drafts
.
count
.
returns
(
1
);
fake
Store
.
countDrafts
.
returns
(
1
);
$rootScope
.
$emit
=
sandbox
.
stub
();
$rootScope
.
$emit
=
sandbox
.
stub
();
fakeWindow
.
confirm
.
returns
(
false
);
fakeWindow
.
confirm
.
returns
(
false
);
...
@@ -495,17 +488,17 @@ describe('sidebar.components.hypothesis-app', function() {
...
@@ -495,17 +488,17 @@ describe('sidebar.components.hypothesis-app', function() {
it
(
'does not discard drafts if the user cancels the prompt'
,
function
()
{
it
(
'does not discard drafts if the user cancels the prompt'
,
function
()
{
const
ctrl
=
createController
();
const
ctrl
=
createController
();
fake
Drafts
.
count
.
returns
(
1
);
fake
Store
.
countDrafts
.
returns
(
1
);
fakeWindow
.
confirm
.
returns
(
false
);
fakeWindow
.
confirm
.
returns
(
false
);
ctrl
.
logout
();
ctrl
.
logout
();
assert
(
fake
Drafts
.
discard
.
notCalled
);
assert
(
fake
Store
.
discardAllDrafts
.
notCalled
);
});
});
it
(
'does not prompt if there are no drafts'
,
function
()
{
it
(
'does not prompt if there are no drafts'
,
function
()
{
const
ctrl
=
createController
();
const
ctrl
=
createController
();
fake
Drafts
.
count
.
returns
(
0
);
fake
Store
.
countDrafts
.
returns
(
0
);
ctrl
.
logout
();
ctrl
.
logout
();
...
@@ -541,7 +534,7 @@ describe('sidebar.components.hypothesis-app', function() {
...
@@ -541,7 +534,7 @@ describe('sidebar.components.hypothesis-app', function() {
});
});
it
(
'does not send LOGOUT_REQUESTED if the user cancels the prompt'
,
function
()
{
it
(
'does not send LOGOUT_REQUESTED if the user cancels the prompt'
,
function
()
{
fake
Drafts
.
count
.
returns
(
1
);
fake
Store
.
countDrafts
.
returns
(
1
);
fakeWindow
.
confirm
.
returns
(
false
);
fakeWindow
.
confirm
.
returns
(
false
);
createController
().
logout
();
createController
().
logout
();
...
...
src/sidebar/index.js
View file @
11ab71ea
...
@@ -211,7 +211,6 @@ function startAngularApp(config) {
...
@@ -211,7 +211,6 @@ function startAngularApp(config) {
.
service
(
'apiRoutes'
,
require
(
'./services/api-routes'
))
.
service
(
'apiRoutes'
,
require
(
'./services/api-routes'
))
.
service
(
'auth'
,
require
(
'./services/oauth-auth'
))
.
service
(
'auth'
,
require
(
'./services/oauth-auth'
))
.
service
(
'bridge'
,
require
(
'../shared/bridge'
))
.
service
(
'bridge'
,
require
(
'../shared/bridge'
))
.
service
(
'drafts'
,
require
(
'./services/drafts'
))
.
service
(
'features'
,
require
(
'./services/features'
))
.
service
(
'features'
,
require
(
'./services/features'
))
.
service
(
'flash'
,
require
(
'./services/flash'
))
.
service
(
'flash'
,
require
(
'./services/flash'
))
.
service
(
'frameSync'
,
require
(
'./services/frame-sync'
).
default
)
.
service
(
'frameSync'
,
require
(
'./services/frame-sync'
).
default
)
...
...
src/sidebar/services/drafts.js
deleted
100644 → 0
View file @
b5d16912
'use strict'
;
/**
* Return true if a given `draft` is empty and can be discarded without losing
* any user input
*/
function
isEmpty
(
draft
)
{
if
(
!
draft
)
{
return
true
;
}
return
!
draft
.
text
&&
draft
.
tags
.
length
===
0
;
}
/**
* The drafts service provides temporary storage for unsaved edits to new or
* existing annotations.
*
* A draft consists of:
*
* 1. `model` which is the original annotation domain model object which the
* draft is associated with. Domain model objects are never returned from
* the drafts service, they're only used to identify the correct draft to
* return.
*
* 2. `isPrivate` (boolean), `tags` (array of objects) and `text` (string)
* which are the user's draft changes to the annotation. These are returned
* from the drafts service by `drafts.get()`.
*
*/
function
DraftStore
()
{
this
.
_drafts
=
[];
/**
* Returns true if 'draft' is a draft for a given
* annotation.
*
* Annotations are matched by ID or local tag.
*/
function
match
(
draft
,
model
)
{
return
(
(
draft
.
model
.
$tag
&&
model
.
$tag
===
draft
.
model
.
$tag
)
||
(
draft
.
model
.
id
&&
model
.
id
===
draft
.
model
.
id
)
);
}
/**
* Returns the number of drafts - both unsaved new annotations, and unsaved
* edits to saved annotations - currently stored.
*/
this
.
count
=
function
count
()
{
return
this
.
_drafts
.
length
;
};
/**
* Returns a list of local tags of new annotations for which unsaved drafts
* exist.
*
* @return {Array<{$tag: string}>}
*/
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
(
let
i
=
0
;
i
<
this
.
_drafts
.
length
;
i
++
)
{
const
draft
=
this
.
_drafts
[
i
];
if
(
match
(
draft
,
model
))
{
return
{
isPrivate
:
draft
.
isPrivate
,
tags
:
draft
.
tags
,
text
:
draft
.
text
,
};
}
}
return
null
;
};
/**
* Returns the draft changes for an annotation, or null if no draft exists
* or the draft is empty.
*/
this
.
getIfNotEmpty
=
function
(
model
)
{
const
draft
=
this
.
get
(
model
);
return
isEmpty
(
draft
)
?
null
:
draft
;
};
/**
* Update the draft version for a given annotation, replacing any
* existing draft.
*/
this
.
update
=
function
update
(
model
,
changes
)
{
const
newDraft
=
{
model
:
{
id
:
model
.
id
,
$tag
:
model
.
$tag
},
isPrivate
:
changes
.
isPrivate
,
tags
:
changes
.
tags
,
text
:
changes
.
text
,
};
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
);
});
};
/** Remove all drafts. */
this
.
discard
=
function
discard
()
{
this
.
_drafts
=
[];
};
}
module
.
exports
=
function
()
{
return
new
DraftStore
();
};
src/sidebar/services/root-thread.js
View file @
11ab71ea
...
@@ -40,7 +40,7 @@ const sortFns = {
...
@@ -40,7 +40,7 @@ const sortFns = {
* The root thread is then displayed by viewer.html
* The root thread is then displayed by viewer.html
*/
*/
// @ngInject
// @ngInject
function
RootThread
(
$rootScope
,
store
,
drafts
,
searchFilter
,
viewFilter
)
{
function
RootThread
(
$rootScope
,
store
,
searchFilter
,
viewFilter
)
{
/**
/**
* Build the root conversation thread from the given UI state.
* Build the root conversation thread from the given UI state.
*
*
...
@@ -86,10 +86,10 @@ function RootThread($rootScope, store, drafts, searchFilter, viewFilter) {
...
@@ -86,10 +86,10 @@ function RootThread($rootScope, store, drafts, searchFilter, viewFilter) {
store
store
.
getState
()
.
getState
()
.
annotations
.
filter
(
function
(
ann
)
{
.
annotations
.
filter
(
function
(
ann
)
{
return
metadata
.
isNew
(
ann
)
&&
!
drafts
.
ge
tIfNotEmpty
(
ann
);
return
metadata
.
isNew
(
ann
)
&&
!
store
.
getDraf
tIfNotEmpty
(
ann
);
})
})
.
forEach
(
function
(
ann
)
{
.
forEach
(
function
(
ann
)
{
drafts
.
remove
(
ann
);
store
.
removeDraft
(
ann
);
$rootScope
.
$broadcast
(
events
.
ANNOTATION_DELETED
,
ann
);
$rootScope
.
$broadcast
(
events
.
ANNOTATION_DELETED
,
ann
);
});
});
}
}
...
...
src/sidebar/services/test/drafts-test.js
deleted
100644 → 0
View file @
b5d16912
'use strict'
;
const
draftsService
=
require
(
'../drafts'
);
const
fixtures
=
{
draftWithText
:
{
isPrivate
:
false
,
text
:
'some text'
,
tags
:
[],
},
draftWithTags
:
{
isPrivate
:
false
,
text
:
''
,
tags
:
[
'atag'
],
},
emptyDraft
:
{
isPrivate
:
false
,
text
:
''
,
tags
:
[],
},
};
describe
(
'drafts'
,
function
()
{
let
drafts
;
beforeEach
(
function
()
{
drafts
=
draftsService
();
});
describe
(
'#getIfNotEmpty'
,
function
()
{
it
(
'returns the draft if it has tags'
,
function
()
{
const
model
=
{
id
:
'foo'
};
drafts
.
update
(
model
,
fixtures
.
draftWithTags
);
assert
.
deepEqual
(
drafts
.
getIfNotEmpty
(
model
),
fixtures
.
draftWithTags
);
});
it
(
'returns the draft if it has text'
,
function
()
{
const
model
=
{
id
:
'foo'
};
drafts
.
update
(
model
,
fixtures
.
draftWithText
);
assert
.
deepEqual
(
drafts
.
getIfNotEmpty
(
model
),
fixtures
.
draftWithText
);
});
it
(
'returns null if the text and tags are empty'
,
function
()
{
const
model
=
{
id
:
'foo'
};
drafts
.
update
(
model
,
fixtures
.
emptyDraft
);
assert
.
isNull
(
drafts
.
getIfNotEmpty
(
model
));
});
});
describe
(
'#update'
,
function
()
{
it
(
'should save changes'
,
function
()
{
const
model
=
{
id
:
'foo'
};
assert
.
notOk
(
drafts
.
get
(
model
));
drafts
.
update
(
model
,
{
isPrivate
:
true
,
tags
:
[
'foo'
],
text
:
'edit'
});
assert
.
deepEqual
(
drafts
.
get
(
model
),
{
isPrivate
:
true
,
tags
:
[
'foo'
],
text
:
'edit'
,
});
});
it
(
'should replace existing drafts'
,
function
()
{
const
model
=
{
id
:
'foo'
};
drafts
.
update
(
model
,
{
isPrivate
:
true
,
tags
:
[
'foo'
],
text
:
'foo'
});
drafts
.
update
(
model
,
{
isPrivate
:
true
,
tags
:
[
'foo'
],
text
:
'bar'
});
assert
.
equal
(
drafts
.
get
(
model
).
text
,
'bar'
);
});
it
(
'should replace existing drafts with the same ID'
,
function
()
{
const
modelA
=
{
id
:
'foo'
};
const
modelB
=
{
id
:
'foo'
};
drafts
.
update
(
modelA
,
{
isPrivate
:
true
,
tags
:
[
'foo'
],
text
:
'foo'
});
drafts
.
update
(
modelB
,
{
isPrivate
:
true
,
tags
:
[
'foo'
],
text
:
'bar'
});
assert
.
equal
(
drafts
.
get
(
modelA
).
text
,
'bar'
);
});
it
(
'should replace drafts with the same tag'
,
function
()
{
const
modelA
=
{
$tag
:
'foo'
};
const
modelB
=
{
$tag
:
'foo'
};
drafts
.
update
(
modelA
,
{
isPrivate
:
true
,
tags
:
[
'foo'
],
text
:
'foo'
});
drafts
.
update
(
modelB
,
{
isPrivate
:
true
,
tags
:
[
'foo'
],
text
:
'bar'
});
assert
.
equal
(
drafts
.
get
(
modelA
).
text
,
'bar'
);
});
});
describe
(
'#remove'
,
function
()
{
it
(
'should remove drafts'
,
function
()
{
const
model
=
{
id
:
'foo'
};
drafts
.
update
(
model
,
{
text
:
'bar'
});
drafts
.
remove
(
model
);
assert
.
notOk
(
drafts
.
get
(
model
));
});
});
describe
(
'#unsaved'
,
function
()
{
it
(
'should return drafts for unsaved annotations'
,
function
()
{
const
model
=
{
$tag
:
'local-tag'
,
id
:
undefined
};
drafts
.
update
(
model
,
{
text
:
'bar'
});
assert
.
deepEqual
(
drafts
.
unsaved
(),
[
model
]);
});
it
(
'should not return drafts for saved annotations'
,
function
()
{
const
model
=
{
id
:
'foo'
};
drafts
.
update
(
model
,
{
text
:
'baz'
});
assert
.
deepEqual
(
drafts
.
unsaved
(),
[]);
});
});
});
src/sidebar/services/test/root-thread-test.js
View file @
11ab71ea
...
@@ -27,7 +27,6 @@ const fixtures = immutable({
...
@@ -27,7 +27,6 @@ const fixtures = immutable({
describe
(
'rootThread'
,
function
()
{
describe
(
'rootThread'
,
function
()
{
let
fakeStore
;
let
fakeStore
;
let
fakeBuildThread
;
let
fakeBuildThread
;
let
fakeDrafts
;
let
fakeSearchFilter
;
let
fakeSearchFilter
;
let
fakeViewFilter
;
let
fakeViewFilter
;
...
@@ -62,15 +61,12 @@ describe('rootThread', function() {
...
@@ -62,15 +61,12 @@ describe('rootThread', function() {
addAnnotations
:
sinon
.
stub
(),
addAnnotations
:
sinon
.
stub
(),
setCollapsed
:
sinon
.
stub
(),
setCollapsed
:
sinon
.
stub
(),
selectTab
:
sinon
.
stub
(),
selectTab
:
sinon
.
stub
(),
getDraftIfNotEmpty
:
sinon
.
stub
().
returns
(
null
),
removeDraft
:
sinon
.
stub
(),
};
};
fakeBuildThread
=
sinon
.
stub
().
returns
(
fixtures
.
emptyThread
);
fakeBuildThread
=
sinon
.
stub
().
returns
(
fixtures
.
emptyThread
);
fakeDrafts
=
{
getIfNotEmpty
:
sinon
.
stub
().
returns
(
null
),
remove
:
sinon
.
stub
(),
};
fakeSearchFilter
=
{
fakeSearchFilter
=
{
generateFacetedFilter
:
sinon
.
stub
(),
generateFacetedFilter
:
sinon
.
stub
(),
};
};
...
@@ -82,7 +78,6 @@ describe('rootThread', function() {
...
@@ -82,7 +78,6 @@ describe('rootThread', function() {
angular
angular
.
module
(
'app'
,
[])
.
module
(
'app'
,
[])
.
value
(
'store'
,
fakeStore
)
.
value
(
'store'
,
fakeStore
)
.
value
(
'drafts'
,
fakeDrafts
)
.
value
(
'searchFilter'
,
fakeSearchFilter
)
.
value
(
'searchFilter'
,
fakeSearchFilter
)
.
value
(
'viewFilter'
,
fakeViewFilter
)
.
value
(
'viewFilter'
,
fakeViewFilter
)
.
service
(
'rootThread'
,
rootThreadFactory
);
.
service
(
'rootThread'
,
rootThreadFactory
);
...
@@ -401,16 +396,16 @@ describe('rootThread', function() {
...
@@ -401,16 +396,16 @@ describe('rootThread', function() {
});
});
it
(
'removes drafts for new and empty annotations'
,
function
()
{
it
(
'removes drafts for new and empty annotations'
,
function
()
{
fake
Drafts
.
ge
tIfNotEmpty
.
returns
(
null
);
fake
Store
.
getDraf
tIfNotEmpty
.
returns
(
null
);
const
annotation
=
annotationFixtures
.
newEmptyAnnotation
();
const
annotation
=
annotationFixtures
.
newEmptyAnnotation
();
$rootScope
.
$broadcast
(
events
.
BEFORE_ANNOTATION_CREATED
,
annotation
);
$rootScope
.
$broadcast
(
events
.
BEFORE_ANNOTATION_CREATED
,
annotation
);
assert
.
calledWith
(
fake
Drafts
.
remove
,
existingNewAnnot
);
assert
.
calledWith
(
fake
Store
.
removeDraft
,
existingNewAnnot
);
});
});
it
(
'deletes new and empty annotations'
,
function
()
{
it
(
'deletes new and empty annotations'
,
function
()
{
fake
Drafts
.
ge
tIfNotEmpty
.
returns
(
null
);
fake
Store
.
getDraf
tIfNotEmpty
.
returns
(
null
);
const
annotation
=
annotationFixtures
.
newEmptyAnnotation
();
const
annotation
=
annotationFixtures
.
newEmptyAnnotation
();
$rootScope
.
$broadcast
(
events
.
BEFORE_ANNOTATION_CREATED
,
annotation
);
$rootScope
.
$broadcast
(
events
.
BEFORE_ANNOTATION_CREATED
,
annotation
);
...
@@ -419,14 +414,14 @@ describe('rootThread', function() {
...
@@ -419,14 +414,14 @@ describe('rootThread', function() {
});
});
it
(
'does not remove annotations that have non-empty drafts'
,
function
()
{
it
(
'does not remove annotations that have non-empty drafts'
,
function
()
{
fake
Drafts
.
ge
tIfNotEmpty
.
returns
(
fixtures
.
nonEmptyDraft
);
fake
Store
.
getDraf
tIfNotEmpty
.
returns
(
fixtures
.
nonEmptyDraft
);
$rootScope
.
$broadcast
(
$rootScope
.
$broadcast
(
events
.
BEFORE_ANNOTATION_CREATED
,
events
.
BEFORE_ANNOTATION_CREATED
,
annotationFixtures
.
newAnnotation
()
annotationFixtures
.
newAnnotation
()
);
);
assert
.
notCalled
(
fake
Drafts
.
remove
);
assert
.
notCalled
(
fake
Store
.
removeDraft
);
assert
.
notCalled
(
onDelete
);
assert
.
notCalled
(
onDelete
);
});
});
...
@@ -439,7 +434,7 @@ describe('rootThread', function() {
...
@@ -439,7 +434,7 @@ describe('rootThread', function() {
annotationFixtures
.
newAnnotation
()
annotationFixtures
.
newAnnotation
()
);
);
assert
.
notCalled
(
fake
Drafts
.
remove
);
assert
.
notCalled
(
fake
Store
.
removeDraft
);
assert
.
notCalled
(
onDelete
);
assert
.
notCalled
(
onDelete
);
});
});
});
});
...
...
src/sidebar/store/index.js
View file @
11ab71ea
...
@@ -37,6 +37,7 @@ const debugMiddleware = require('./debug-middleware');
...
@@ -37,6 +37,7 @@ const debugMiddleware = require('./debug-middleware');
const
activity
=
require
(
'./modules/activity'
);
const
activity
=
require
(
'./modules/activity'
);
const
annotations
=
require
(
'./modules/annotations'
);
const
annotations
=
require
(
'./modules/annotations'
);
const
directLinked
=
require
(
'./modules/direct-linked'
);
const
directLinked
=
require
(
'./modules/direct-linked'
);
const
drafts
=
require
(
'./modules/drafts'
);
const
frames
=
require
(
'./modules/frames'
);
const
frames
=
require
(
'./modules/frames'
);
const
links
=
require
(
'./modules/links'
);
const
links
=
require
(
'./modules/links'
);
const
groups
=
require
(
'./modules/groups'
);
const
groups
=
require
(
'./modules/groups'
);
...
@@ -88,6 +89,7 @@ function store($rootScope, settings) {
...
@@ -88,6 +89,7 @@ function store($rootScope, settings) {
activity
,
activity
,
annotations
,
annotations
,
directLinked
,
directLinked
,
drafts
,
frames
,
frames
,
links
,
links
,
groups
,
groups
,
...
...
src/sidebar/store/modules/drafts.js
0 → 100644
View file @
11ab71ea
'use strict'
;
const
util
=
require
(
'../util'
);
/**
* The drafts store provides temporary storage for unsaved edits to new or
* existing annotations.
*/
function
init
()
{
return
{
drafts
:
[],
};
}
/**
* Helper class to encapsulate the draft properties and a few simple methods.
*
* A draft consists of:
*
* 1. `annotation` which is the original annotation object which the
* draft is associated with. If this is just a draft, then this may
* not have an id yet and instead, $tag is used.
*
* 2. `isPrivate` (boolean), `tags` (array of objects) and `text` (string)
* which are the user's draft changes to the annotation. These are returned
* from the drafts store selector by `drafts.getDraft()`.
*/
class
Draft
{
constructor
(
annotation
,
changes
)
{
this
.
annotation
=
{
id
:
annotation
.
id
,
$tag
:
annotation
.
$tag
};
this
.
isPrivate
=
changes
.
isPrivate
;
this
.
tags
=
changes
.
tags
;
this
.
text
=
changes
.
text
;
}
/**
* Returns true if this draft matches a given annotation.
*
* Annotations are matched by ID or local tag.
*/
match
(
annotation
)
{
return
(
(
this
.
annotation
.
$tag
&&
annotation
.
$tag
===
this
.
annotation
.
$tag
)
||
(
this
.
annotation
.
id
&&
annotation
.
id
===
this
.
annotation
.
id
)
);
}
/**
* Return true if this draft is empty and can be discarded without losing
* any user input.
*/
isEmpty
()
{
return
!
this
.
text
&&
this
.
tags
.
length
===
0
;
}
}
/* Reducer */
const
update
=
{
DISCARD_ALL_DRAFTS
:
function
()
{
return
{
drafts
:
[],
};
},
REMOVE_DRAFT
:
function
(
state
,
action
)
{
const
drafts
=
state
.
drafts
.
filter
(
draft
=>
{
return
!
draft
.
match
(
action
.
annotation
);
});
return
{
drafts
,
};
},
UPDATE_DRAFT
:
function
(
state
,
action
)
{
// removes a matching existing draft, then adds
const
drafts
=
state
.
drafts
.
filter
(
draft
=>
{
return
!
draft
.
match
(
action
.
draft
.
annotation
);
});
drafts
.
push
(
action
.
draft
);
// push ok since its a copy
return
{
drafts
,
};
},
};
const
actions
=
util
.
actionTypes
(
update
);
/* Actions */
/**
* Create or update the draft version for a given annotation by
* replacing any existing draft or simply creating a new one.
*/
function
createDraft
(
annotation
,
changes
)
{
return
{
type
:
actions
.
UPDATE_DRAFT
,
draft
:
new
Draft
(
annotation
,
changes
),
};
}
/** Remove all drafts. */
function
discardAllDrafts
()
{
return
{
type
:
actions
.
DISCARD_ALL_DRAFTS
,
};
}
/** Remove the draft version of an annotation. */
function
removeDraft
(
annotation
)
{
return
{
type
:
actions
.
REMOVE_DRAFT
,
annotation
,
};
}
/* Selectors */
/**
* Returns the number of drafts - both unsaved new annotations, and unsaved
* edits to saved annotations - currently stored.
*
* @return {number}
*/
function
countDrafts
(
state
)
{
return
state
.
drafts
.
length
;
}
/**
* Retrieve the draft changes for an annotation.
*
* @return {Draft|null}
*/
function
getDraft
(
state
,
annotation
)
{
for
(
let
i
=
0
;
i
<
state
.
drafts
.
length
;
i
++
)
{
const
draft
=
state
.
drafts
[
i
];
if
(
draft
.
match
(
annotation
))
{
return
draft
;
}
}
return
null
;
}
/**
* Returns the draft changes for an annotation, or null if no draft exists
* or the draft is empty.
*
* @return {Draft|null}
*/
function
getDraftIfNotEmpty
(
state
,
annotation
)
{
const
draft
=
getDraft
(
state
,
annotation
);
if
(
!
draft
)
{
return
null
;
}
return
draft
.
isEmpty
()
?
null
:
draft
;
}
/**
* Returns a list of draft annotations which have no id.
*
* @return {Object[]}
*/
function
unsavedAnnotations
(
state
)
{
return
state
.
drafts
.
filter
(
draft
=>
!
draft
.
annotation
.
id
)
.
map
(
draft
=>
draft
.
annotation
);
}
module
.
exports
=
{
init
,
update
,
actions
:
{
createDraft
,
discardAllDrafts
,
removeDraft
,
},
selectors
:
{
countDrafts
,
getDraft
,
getDraftIfNotEmpty
,
unsavedAnnotations
,
},
Draft
,
};
src/sidebar/store/modules/test/drafts-test.js
0 → 100644
View file @
11ab71ea
'use strict'
;
const
immutable
=
require
(
'seamless-immutable'
);
const
drafts
=
require
(
'../drafts'
);
const
{
Draft
}
=
require
(
'../drafts'
);
const
createStore
=
require
(
'../../create-store'
);
const
fixtures
=
immutable
({
draftWithText
:
{
isPrivate
:
false
,
text
:
'some text'
,
tags
:
[],
},
draftWithTags
:
{
isPrivate
:
false
,
text
:
''
,
tags
:
[
'atag'
],
},
emptyDraft
:
{
isPrivate
:
false
,
text
:
''
,
tags
:
[],
},
annotation
:
{
id
:
'my_annotation'
,
$tag
:
'my_annotation_tag'
,
},
});
describe
(
'Drafts Store'
,
()
=>
{
let
store
;
beforeEach
(()
=>
{
store
=
createStore
([
drafts
]);
});
describe
(
'Draft'
,
()
=>
{
it
(
'constructor'
,
()
=>
{
const
draft
=
new
Draft
(
fixtures
.
annotation
,
fixtures
.
draftWithText
);
assert
.
deepEqual
(
draft
,
{
annotation
:
{
...
fixtures
.
annotation
,
},
...
fixtures
.
draftWithText
,
});
});
describe
(
'#isEmpty'
,
()
=>
{
it
(
'returns false if draft has tags or text'
,
()
=>
{
const
draft
=
new
Draft
(
fixtures
.
annotation
,
fixtures
.
draftWithText
);
assert
.
isFalse
(
draft
.
isEmpty
());
});
it
(
'returns true if draft has no tags or text'
,
()
=>
{
const
draft
=
new
Draft
(
fixtures
.
annotation
,
fixtures
.
emptyDraft
);
assert
.
isTrue
(
draft
.
isEmpty
());
});
});
describe
(
'#match'
,
()
=>
{
it
(
'matches an annotation with the same tag or id'
,
()
=>
{
const
draft
=
new
Draft
(
fixtures
.
annotation
,
fixtures
.
draftWithText
);
assert
.
isTrue
(
draft
.
match
({
id
:
fixtures
.
annotation
.
id
,
})
);
assert
.
isTrue
(
draft
.
match
({
$tag
:
fixtures
.
annotation
.
$tag
,
})
);
});
it
(
'does not match an annotation with a different tag or id'
,
()
=>
{
const
draft
=
new
Draft
(
fixtures
.
annotation
,
fixtures
.
draftWithText
);
assert
.
isFalse
(
draft
.
match
({
id
:
'fake'
,
})
);
assert
.
isFalse
(
draft
.
match
({
$tag
:
'fake'
,
})
);
});
});
});
describe
(
'#getDraftIfNotEmpty'
,
()
=>
{
it
(
'returns the draft if it has tags'
,
()
=>
{
store
.
createDraft
(
fixtures
.
annotation
,
fixtures
.
draftWithTags
);
assert
.
deepEqual
(
store
.
getDraftIfNotEmpty
(
fixtures
.
annotation
).
annotation
,
fixtures
.
annotation
);
});
it
(
'returns the draft if it has text'
,
()
=>
{
store
.
createDraft
(
fixtures
.
annotation
,
fixtures
.
draftWithText
);
assert
.
deepEqual
(
store
.
getDraftIfNotEmpty
(
fixtures
.
annotation
).
annotation
,
fixtures
.
annotation
);
});
it
(
'returns null if the text and tags are empty'
,
()
=>
{
store
.
createDraft
(
fixtures
.
annotation
,
fixtures
.
emptyDraft
);
assert
.
isNull
(
store
.
getDraftIfNotEmpty
(
fixtures
.
annotation
));
});
it
(
'returns null if there is no matching draft'
,
()
=>
{
assert
.
isNull
(
store
.
getDraftIfNotEmpty
(
'fake'
));
});
});
describe
(
'#createDraft'
,
()
=>
{
it
(
'should save changes'
,
()
=>
{
assert
.
notOk
(
store
.
getDraft
(
fixtures
.
annotation
));
store
.
createDraft
(
fixtures
.
annotation
,
fixtures
.
draftWithText
);
assert
.
deepEqual
(
store
.
getDraft
(
fixtures
.
annotation
),
new
Draft
(
fixtures
.
annotation
,
fixtures
.
draftWithText
)
);
});
it
(
'should replace existing drafts with the same ID'
,
()
=>
{
const
fakeAnnotation
=
{
id
:
'my_annotation'
,
};
const
fakeDraft
=
{
isPrivate
:
true
,
tags
:
[
'foo'
],
text
:
''
,
};
store
.
createDraft
(
fakeAnnotation
,
{
...
fakeDraft
,
text
:
'foo'
,
});
assert
.
equal
(
store
.
getDraft
(
fakeAnnotation
).
text
,
'foo'
);
// now replace the draft
store
.
createDraft
(
fakeAnnotation
,
{
...
fakeDraft
,
text
:
'bar'
,
});
assert
.
equal
(
store
.
getDraft
(
fakeAnnotation
).
text
,
'bar'
);
});
it
(
'should replace existing drafts with the same tag'
,
()
=>
{
const
fakeAnnotation
=
{
$tag
:
'my_annotation_tag'
,
};
const
fakeDraft
=
{
isPrivate
:
true
,
tags
:
[
'foo'
],
text
:
''
,
};
store
.
createDraft
(
fakeAnnotation
,
{
...
fakeDraft
,
text
:
'foo'
,
});
assert
.
equal
(
store
.
getDraft
(
fakeAnnotation
).
text
,
'foo'
);
// now replace the draft
store
.
createDraft
(
fakeAnnotation
,
{
...
fakeDraft
,
text
:
'bar'
,
});
assert
.
equal
(
store
.
getDraft
(
fakeAnnotation
).
text
,
'bar'
);
});
});
describe
(
'#countDrafts'
,
()
=>
{
it
(
'should count drafts'
,
()
=>
{
assert
.
equal
(
store
.
countDrafts
(),
0
);
store
.
createDraft
({
id
:
'1'
},
fixtures
.
draftWithText
);
assert
.
equal
(
store
.
countDrafts
(),
1
);
// since same id, this performs a replace, should still be 1 count
store
.
createDraft
({
id
:
'1'
},
fixtures
.
draftWithText
);
assert
.
equal
(
store
.
countDrafts
(),
1
);
store
.
createDraft
({
id
:
'2'
},
fixtures
.
draftWithText
);
assert
.
equal
(
store
.
countDrafts
(),
2
);
});
});
describe
(
'#discardAllDrafts'
,
()
=>
{
it
(
'should remove all drafts'
,
()
=>
{
store
.
createDraft
({
id
:
'1'
},
fixtures
.
draftWithText
);
store
.
createDraft
({
id
:
'2'
},
fixtures
.
draftWithText
);
store
.
discardAllDrafts
(
fixtures
.
annotation
);
assert
.
equal
(
store
.
countDrafts
(),
0
);
});
});
describe
(
'#removeDraft'
,
()
=>
{
it
(
'should remove drafts'
,
()
=>
{
store
.
createDraft
(
fixtures
.
annotation
,
fixtures
.
draftWithText
);
assert
.
isOk
(
store
.
getDraft
(
fixtures
.
annotation
));
store
.
removeDraft
(
fixtures
.
annotation
);
assert
.
isNotOk
(
store
.
getDraft
(
fixtures
.
annotation
));
});
});
describe
(
'#unsavedAnnotations'
,
()
=>
{
it
(
'should return unsaved annotations which have drafts'
,
()
=>
{
const
fakeAnnotation1
=
{
$tag
:
'local-tag1'
,
id
:
undefined
,
};
const
fakeAnnotation2
=
{
$tag
:
'local-tag2'
,
id
:
undefined
,
};
store
.
createDraft
(
fakeAnnotation1
,
fixtures
.
draftWithText
);
store
.
createDraft
(
fakeAnnotation2
,
fixtures
.
draftWithText
);
assert
.
deepEqual
(
store
.
unsavedAnnotations
(),
[
fakeAnnotation1
,
fakeAnnotation2
,
]);
});
it
(
'should not return saved annotations which have drafts'
,
()
=>
{
store
.
createDraft
(
fixtures
.
annotation
,
fixtures
.
draftWithText
);
assert
.
deepEqual
(
store
.
unsavedAnnotations
(),
[]);
});
});
});
src/sidebar/test/integration/threading-test.js
View file @
11ab71ea
...
@@ -54,7 +54,6 @@ describe('annotation threading', function() {
...
@@ -54,7 +54,6 @@ describe('annotation threading', function() {
angular
angular
.
module
(
'app'
,
[])
.
module
(
'app'
,
[])
.
service
(
'store'
,
require
(
'../../store'
))
.
service
(
'store'
,
require
(
'../../store'
))
.
service
(
'drafts'
,
require
(
'../../services/drafts'
))
.
service
(
'rootThread'
,
require
(
'../../services/root-thread'
))
.
service
(
'rootThread'
,
require
(
'../../services/root-thread'
))
.
service
(
'searchFilter'
,
require
(
'../../services/search-filter'
))
.
service
(
'searchFilter'
,
require
(
'../../services/search-filter'
))
.
service
(
'viewFilter'
,
require
(
'../../services/view-filter'
))
.
service
(
'viewFilter'
,
require
(
'../../services/view-filter'
))
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment