Skip to content

Commit 465f392

Browse files
committed
Merge branch 'post-milestone3m' into feature/widget-peoplesearch
2 parents 3b95bae + cb84838 commit 465f392

7 files changed

Lines changed: 131 additions & 7 deletions

File tree

src/login/login.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,10 +1047,20 @@ export function newAppInstance (
10471047
* and/or a developer
10481048
*/
10491049
export async function getUserRoles (): Promise<Array<NamedNode>> {
1050+
const sessionInfo = authSession.info
1051+
if (!sessionInfo?.isLoggedIn || !sessionInfo?.webId) {
1052+
return []
1053+
}
1054+
1055+
const currentUser = authn.currentUser()
1056+
if (!currentUser) {
1057+
return []
1058+
}
1059+
10501060
try {
1051-
const { me, preferencesFile, preferencesFileError } = await ensureLoadedPreferences({})
1061+
const { me, preferencesFile, preferencesFileError } = await ensureLoadedPreferences({ me: currentUser })
10521062
if (!preferencesFile || preferencesFileError) {
1053-
throw new Error(preferencesFileError)
1063+
throw new Error(preferencesFileError || 'Unable to load user preferences file.')
10541064
}
10551065
return solidLogicSingleton.store.each(
10561066
me,

src/v2/components/actions/button/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ export { Button }
44

55
const BUTTON_TAG_NAME = 'solid-ui-button'
66

7+
declare global {
8+
interface HTMLElementTagNameMap {
9+
'solid-ui-button': Button
10+
}
11+
}
12+
713
if (!customElements.get(BUTTON_TAG_NAME)) {
814
customElements.define(BUTTON_TAG_NAME, Button)
915
}

src/widgets/buttons.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,10 @@ export function attachmentList (dom: HTMLDocument, subject: NamedNode, div: HTML
10281028
attachmentLeft.appendChild(paperclip)
10291029
const fhandler = options.uploadFolder ? droppedFileHandler : null
10301030
makeDropTarget(paperclip, droppedURIHandler, fhandler) // beware missing the wire of the paparclip!
1031+
const paperclipImage = paperclip.querySelector('img')
1032+
if (paperclipImage) {
1033+
makeDropTarget(paperclipImage, droppedURIHandler, fhandler)
1034+
}
10311035
makeDropTarget(attachmentLeft, droppedURIHandler, fhandler) // just the outer won't do it
10321036

10331037
if (options.uploadFolder) { // Addd an explicit file upload button as well

src/widgets/dragAndDrop.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,22 @@ import { style } from '../style'
1515
/* global FileReader alert */
1616

1717
export function makeDropTarget (ele, droppedURIHandler, droppedFileHandler) {
18+
const normalizeDroppedUris = function (uriText) {
19+
return uriText
20+
.split('\n')
21+
.map(uri => uri.trim())
22+
.filter(uri => uri && uri[0] !== '#')
23+
}
24+
1825
const dragoverListener = function (e) {
1926
e.preventDefault() // Need this; otherwise, drop does not work.
27+
e.stopPropagation()
2028
e.dataTransfer.dropEffect = 'copy'
2129
}
2230

2331
const dragenterListener = function (e) {
32+
e.preventDefault()
33+
e.stopPropagation()
2434
debug.log('dragenter event dropEffect: ' + e.dataTransfer.dropEffect)
2535
if (this.localStyle) {
2636
// necessary not sure when
@@ -33,6 +43,7 @@ export function makeDropTarget (ele, droppedURIHandler, droppedFileHandler) {
3343
debug.log('dragenter event dropEffect 2: ' + e.dataTransfer.dropEffect)
3444
}
3545
const dragleaveListener = function (e) {
46+
e.stopPropagation()
3647
debug.log('dragleave event dropEffect: ' + e.dataTransfer.dropEffect)
3748
if (this.savedStyle) {
3849
this.localStyle = this.savedStyle
@@ -43,6 +54,7 @@ export function makeDropTarget (ele, droppedURIHandler, droppedFileHandler) {
4354

4455
const dropListener = function (e) {
4556
if (e.preventDefault) e.preventDefault() // stops the browser from redirecting off to the text.
57+
if (e.stopPropagation) e.stopPropagation()
4658
debug.log('Drop event. dropEffect: ' + e.dataTransfer.dropEffect)
4759
debug.log(
4860
'Drop event. types: ' +
@@ -55,7 +67,7 @@ export function makeDropTarget (ele, droppedURIHandler, droppedFileHandler) {
5567
for (let t = 0; t < e.dataTransfer.types.length; t++) {
5668
const type = e.dataTransfer.types[t]
5769
if (type === 'text/uri-list') {
58-
uris = e.dataTransfer.getData(type).split('\n') // @ ignore those starting with #
70+
uris = normalizeDroppedUris(e.dataTransfer.getData(type))
5971
debug.log('Dropped text/uri-list: ' + uris)
6072
} else if (type === 'text/plain') {
6173
text = e.dataTransfer.getData(type)
@@ -79,13 +91,14 @@ export function makeDropTarget (ele, droppedURIHandler, droppedFileHandler) {
7991
droppedFileHandler(files)
8092
}
8193
}
82-
if (uris === null && text && text.slice(0, 4) === 'http') {
83-
uris = text
94+
const trimmedText = text ? text.trim() : ''
95+
if (uris === null && trimmedText && trimmedText.slice(0, 4) === 'http') {
96+
uris = [trimmedText]
8497
debug.log('Waring: Poor man\'s drop: using text for URI') // chrome disables text/uri-list??
8598
}
8699
} else {
87100
// ... however, if we're IE, we don't have the .types property, so we'll just get the Text value
88-
uris = [e.dataTransfer.getData('Text')]
101+
uris = normalizeDroppedUris(e.dataTransfer.getData('Text'))
89102
debug.log('WARNING non-standard drop event: ' + uris[0])
90103
}
91104
debug.log('Dropped URI list (2): ' + uris)

src/widgets/forms.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,7 +1903,14 @@ export function buildCheckboxForm (dom, kb, lab, del, ins, form, dataDoc, trista
19031903
refresh()
19041904
if (!editable) return box
19051905

1906+
let isUpdating = false // Prevent concurrent updates on double-click
1907+
19061908
const boxHandler = function (_e) {
1909+
if (isUpdating) {
1910+
return // Ignore clicks while update is in progress
1911+
}
1912+
isUpdating = true
1913+
input.disabled = true // Disable button to provide user feedback
19071914
colorCarrier.style.color = '#bbb' // grey -- not saved yet
19081915
const toDelete = input.state === true ? ins : input.state === false ? del : []
19091916
input.newState =
@@ -1924,6 +1931,8 @@ export function buildCheckboxForm (dom, kb, lab, del, ins, form, dataDoc, trista
19241931
success,
19251932
errorBody
19261933
) {
1934+
isUpdating = false
1935+
input.disabled = false
19271936
if (!success) {
19281937
if (toDelete.why) {
19291938
const hmmm = kb.holds(

test/unit/login/login.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,34 @@ describe('ensureLoggedIn', () => {
1111
expect(testLogin.ensureLoggedIn({})).toBeInstanceOf(Object)
1212
})
1313
})
14+
15+
describe('getUserRoles', () => {
16+
afterEach(() => {
17+
jest.restoreAllMocks()
18+
jest.resetModules()
19+
})
20+
21+
it('returns [] and does not load preferences when current user is missing', async () => {
22+
const solidLogic = require('solid-logic')
23+
24+
solidLogic.authSession.info = {
25+
isLoggedIn: true,
26+
webId: 'https://alice.example.com/profile/card#me'
27+
}
28+
29+
const currentUserSpy = jest
30+
.spyOn(solidLogic.authn, 'currentUser')
31+
.mockReturnValue(null)
32+
const loadPreferencesSpy = jest.spyOn(
33+
solidLogic.solidLogicSingleton.profile,
34+
'loadPreferences'
35+
)
36+
37+
const loginModule = require('../../../src/login/login')
38+
const roles = await loginModule.getUserRoles()
39+
40+
expect(currentUserSpy).toHaveBeenCalled()
41+
expect(roles).toEqual([])
42+
expect(loadPreferencesSpy).not.toHaveBeenCalled()
43+
})
44+
})

test/unit/widgets/forms/index.test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { silenceDebugMessages } from '../../helpers/debugger'
2-
import { namedNode } from 'rdflib'
2+
import { namedNode, st } from 'rdflib'
33
import ns from '../../../../src/ns'
44
import { store } from 'solid-logic'
55

@@ -594,6 +594,57 @@ describe('buildCheckboxForm', () => {
594594
)
595595
).toBeInstanceOf(HTMLDivElement)
596596
})
597+
598+
it('ignores rapid second click while async update is in progress and reenables button afterward', async () => {
599+
const dataDoc = namedNode('http://example.com/#doc')
600+
const form = namedNode('http://example.com/#form')
601+
const subject = namedNode('http://example.com/#subject')
602+
const predicate = namedNode('http://example.com/#predicate')
603+
const object = namedNode('http://example.com/#object')
604+
const statement = st(subject, predicate, object, dataDoc)
605+
606+
const originalEditable = store.updater.editable
607+
const originalUpdate = store.updater.update
608+
609+
const updateSpy = jest.fn((_deletes, _inserts, callback) => {
610+
return new Promise(resolve => {
611+
setTimeout(() => {
612+
callback(undefined, true, 'ok')
613+
resolve(true)
614+
}, 0)
615+
})
616+
})
617+
618+
store.updater.editable = jest.fn(() => true) as any
619+
store.updater.update = updateSpy as any
620+
621+
try {
622+
const box = buildCheckboxForm(
623+
document,
624+
store,
625+
'label',
626+
[],
627+
statement,
628+
form,
629+
dataDoc,
630+
false
631+
)
632+
const checkboxButton = box.querySelector('button') as HTMLButtonElement
633+
634+
checkboxButton.click()
635+
checkboxButton.click()
636+
637+
expect(updateSpy).toHaveBeenCalledTimes(1)
638+
expect(checkboxButton.disabled).toEqual(true)
639+
640+
await new Promise(resolve => setTimeout(resolve, 5))
641+
642+
expect(checkboxButton.disabled).toEqual(false)
643+
} finally {
644+
store.updater.editable = originalEditable
645+
store.updater.update = originalUpdate
646+
}
647+
})
597648
})
598649

599650
describe('newThing', () => {

0 commit comments

Comments
 (0)