diff --git a/app/assets/javascripts/components/SessionsModal.tsx b/app/assets/javascripts/components/SessionsModal.tsx index 52cbf4533..67c534a9e 100644 --- a/app/assets/javascripts/components/SessionsModal.tsx +++ b/app/assets/javascripts/components/SessionsModal.tsx @@ -40,14 +40,14 @@ function useSessions( (async () => { setRefreshing(true); const response = await application.getSessions(); - if ('error' in response) { + if ('error' in response || !response.data) { if (response.error?.message) { setErrorMessage(response.error.message); } else { setErrorMessage('An unknown error occured while loading sessions.'); } } else { - const sessions = response as Session[]; + const sessions = response.data; setSessions(sessions); setErrorMessage(''); } diff --git a/app/assets/javascripts/services/archiveManager.ts b/app/assets/javascripts/services/archiveManager.ts index d80635a1d..e8f5f6de1 100644 --- a/app/assets/javascripts/services/archiveManager.ts +++ b/app/assets/javascripts/services/archiveManager.ts @@ -7,21 +7,25 @@ import { PayloadContent, } from '@standardnotes/snjs'; -function zippableTxtName(name: string, suffix = ""): string { - const sanitizedName = name - .replace(/\//g, '') - .replace(/\\+/g, '') - .replace(/:/g, ' ') - .replace(/\./g, ' '); - const nameEnd = suffix + ".txt"; +function sanitizeFileName(name: string) { + return name + .replace(/\//g, '') + .replace(/\\+/g, '') + .replace(/:/g, ' ') + .replace(/\|/g, ' ') + .replace(/\./g, ' '); +} + +function zippableTxtName(name: string, suffix = ''): string { + const sanitizedName = sanitizeFileName(name); + const nameEnd = suffix + '.txt'; const maxFileNameLength = 255; return sanitizedName.slice(0, maxFileNameLength - nameEnd.length) + nameEnd; } export class ArchiveManager { - - private readonly application: WebApplication - private textFile?: string + private readonly application: WebApplication; + private textFile?: string; constructor(application: WebApplication) { this.application = application; @@ -36,10 +40,9 @@ export class ArchiveManager { if (!data) { return; } - const blobData = new Blob( - [JSON.stringify(data, null, 2)], - { type: 'text/json' } - ); + const blobData = new Blob([JSON.stringify(data, null, 2)], { + type: 'text/json', + }); if (encrypted) { this.downloadData( blobData, @@ -85,21 +88,18 @@ export class ArchiveManager { }); } - private async downloadZippedDecryptedItems( - data: BackupFile - ) { + private async downloadZippedDecryptedItems(data: BackupFile) { await this.loadZip(); const items = data.items; this.zip.createWriter( new this.zip.BlobWriter('application/zip'), async (zipWriter: any) => { await new Promise((resolve) => { - const blob = new Blob( - [JSON.stringify(data, null, 2)], - { type: 'text/plain' } - ); + const blob = new Blob([JSON.stringify(data, null, 2)], { + type: 'text/plain', + }); const fileName = zippableTxtName( - 'Standard Notes Backup and Import File.txt' + 'Standard Notes Backup and Import File' ); zipWriter.add(fileName, new this.zip.BlobReader(blob), resolve); }); @@ -120,7 +120,8 @@ export class ArchiveManager { name = ''; } const blob = new Blob([contents], { type: 'text/plain' }); - const fileName = `Items/${item.content_type}/` + + const fileName = + `Items/${sanitizeFileName(item.content_type)}/` + zippableTxtName(name, `-${item.uuid.split('-')[0]}`); zipWriter.add(fileName, new this.zip.BlobReader(blob), () => { index++; @@ -138,7 +139,9 @@ export class ArchiveManager { }); }; nextFile(); - }, onerror); + }, + onerror + ); } private hrefForData(data: Blob) { diff --git a/app/assets/javascripts/ui_models/app_state.ts b/app/assets/javascripts/ui_models/app_state.ts index 8748bb593..7b48a0ffb 100644 --- a/app/assets/javascripts/ui_models/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state.ts @@ -168,6 +168,7 @@ export class AppState { readonly noAccountWarning: NoAccountWarningState; readonly sync = new SyncState(); isSessionsModalVisible = false; + mouseUp = Promise.resolve(); private appEventObserverRemovers: (() => void)[] = []; @@ -195,6 +196,7 @@ export class AppState { this.notifyEvent(event); }; this.registerVisibilityObservers(); + document.addEventListener('mousedown', this.onMouseDown); if (this.bridge.appVersion.includes('-beta')) { this.showBetaWarning = storage.get(StorageKey.ShowBetaWarning) ?? true; @@ -231,9 +233,16 @@ export class AppState { this.rootScopeCleanup2 = undefined; } document.removeEventListener('visibilitychange', this.onVisibilityChange); + document.removeEventListener('mousedown', this.onMouseDown); this.onVisibilityChange = undefined; } + onMouseDown = (): void => { + this.mouseUp = new Promise((resolve) => { + document.addEventListener('mouseup', () => resolve(), { once: true }); + }); + }; + openSessionsModal() { this.isSessionsModalVisible = true; } @@ -259,7 +268,7 @@ export class AppState { async createEditor(title?: string) { const activeEditor = this.getActiveEditor(); const activeTagUuid = this.selectedTag - ? this.selectedTag.isSmartTag() + ? this.selectedTag.isSmartTag ? undefined : this.selectedTag.uuid : undefined; diff --git a/app/assets/javascripts/ui_models/application.ts b/app/assets/javascripts/ui_models/application.ts index 8cfa78000..38cc85274 100644 --- a/app/assets/javascripts/ui_models/application.ts +++ b/app/assets/javascripts/ui_models/application.ts @@ -63,8 +63,7 @@ export class WebApplication extends SNApplication { WebCrypto, new AlertService(), identifier, - undefined, - undefined, + [], defaultSyncServerHost ); this.$compile = $compile; diff --git a/app/assets/javascripts/views/editor/editor_view.ts b/app/assets/javascripts/views/editor/editor_view.ts index f5ed13f1a..5f36183d7 100644 --- a/app/assets/javascripts/views/editor/editor_view.ts +++ b/app/assets/javascripts/views/editor/editor_view.ts @@ -460,7 +460,7 @@ class EditorViewCtrl extends PureViewCtrl { await this.editor.insertTemplatedNote(); } const selectedTag = this.appState.selectedTag; - if (!selectedTag?.isSmartTag() && !selectedTag?.hasRelationshipWithItem(note)) { + if (!selectedTag?.isSmartTag && !selectedTag?.hasRelationshipWithItem(note)) { await this.application.changeItem( selectedTag!.uuid, (mutator) => { @@ -1052,7 +1052,7 @@ class EditorViewCtrl extends PureViewCtrl { if (savedUuid === this.note.uuid) { const selectedTag = this.appState.selectedTag; if ( - !selectedTag?.isSmartTag() && + !selectedTag?.isSmartTag && !selectedTag?.hasRelationshipWithItem(this.note) ) { this.application.changeAndSaveItem( diff --git a/app/assets/javascripts/views/notes/note_utils.ts b/app/assets/javascripts/views/notes/note_utils.ts deleted file mode 100644 index 6f8265982..000000000 --- a/app/assets/javascripts/views/notes/note_utils.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { SNNote } from '@standardnotes/snjs'; - -export function notePassesFilter( - note: SNNote, - showArchived: boolean, - hidePinned: boolean, - filterText: string -): boolean { - const canShowArchived = showArchived; - const canShowPinned = !hidePinned; - if ((note.archived && !canShowArchived) || (note.pinned && !canShowPinned)) { - return false; - } - if (note.protected) { - const match = noteMatchesQuery(note, filterText); - /** Only match title to prevent leaking protected note text */ - return match === Match.Title || match === Match.TitleAndText; - } else { - return noteMatchesQuery(note, filterText) !== Match.None; - } -} - -enum Match { - None = 0, - Title = 1, - Text = 2, - TitleAndText = Title + Text, - Uuid = 5, -} - -function noteMatchesQuery(note: SNNote, query: string): Match { - if (query.length === 0) { - return Match.TitleAndText; - } - const title = note.safeTitle().toLowerCase(); - const text = note.safeText().toLowerCase(); - const lowercaseText = query.toLowerCase(); - const words = lowercaseText.split(' '); - const quotedText = stringBetweenQuotes(lowercaseText); - if (quotedText) { - return ( - (title.includes(quotedText) ? Match.Title : Match.None) + - (text.includes(quotedText) ? Match.Text : Match.None) - ); - } - if (stringIsUuid(lowercaseText)) { - return note.uuid === lowercaseText ? Match.Uuid : Match.None; - } - const matchesTitle = words.every((word) => { - return title.indexOf(word) >= 0; - }); - const matchesBody = words.every((word) => { - return text.indexOf(word) >= 0; - }); - return (matchesTitle ? Match.Title : 0) + (matchesBody ? Match.Text : 0); -} - -function stringBetweenQuotes(text: string) { - const matches = text.match(/"(.*?)"/); - return matches ? matches[1] : null; -} - -function stringIsUuid(text: string) { - const matches = text.match( - /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/ - ); - // eslint-disable-next-line no-unneeded-ternary - return matches ? true : false; -} diff --git a/app/assets/javascripts/views/notes/notes-view.pug b/app/assets/javascripts/views/notes/notes-view.pug index a42e16bc1..2439b9c8e 100644 --- a/app/assets/javascripts/views/notes/notes-view.pug +++ b/app/assets/javascripts/views/notes/notes-view.pug @@ -1,7 +1,7 @@ #notes-column.sn-component.section.notes(aria-label='Notes') .content #notes-title-bar.section-title-bar - .padded + .p-4.pt-0 .section-title-bar-header .sk-h2.font-semibold.title {{self.state.panelTitle}} .sk-button.contrast.wide( @@ -12,18 +12,34 @@ i.icon.ion-plus.add-button .filter-section(role='search') input#search-bar.filter-bar( - ng-blur='self.onFilterEnter()', + ng-ref='self.searchBarInput' + ng-focus='self.onSearchInputFocus()' + ng-blur='self.onSearchInputBlur()', ng-change='self.filterTextChanged()', ng-keyup='$event.keyCode == 13 && self.onFilterEnter();', ng-model='self.state.noteFilter.text', placeholder='Search', select-on-focus='true', title='Searches notes in the currently selected tag' - ) + ) #search-clear-button( ng-click='self.clearFilterText();', ng-show='self.state.noteFilter.text' ) ✕ + label.sk-panel-row.justify-left.mt-2.animate-slide-in-top( + ng-if='self.state.searchIsFocused || self.state.searchOptionsAreFocused || self.state.authorizingSearchOptions' + style="padding-bottom: 0" + ) + .sk-horizontal-group.tight + input( + ng-ref='self.searchOptionsInput' + ng-focus="self.onSearchOptionsFocus()" + ng-blur="self.onSearchOptionsBlur()" + type="checkbox" + ng-checked="self.state.noteFilter.includeProtectedNoteText" + ng-on-click="self.onIncludeProtectedNoteTextChange($event)" + ) + p.sk-p.capitalize Include protected contents no-account-warning( application='self.application' app-state='self.appState' diff --git a/app/assets/javascripts/views/notes/notes_view.ts b/app/assets/javascripts/views/notes/notes_view.ts index 3bd631cf6..9d71f391b 100644 --- a/app/assets/javascripts/views/notes/notes_view.ts +++ b/app/assets/javascripts/views/notes/notes_view.ts @@ -9,6 +9,8 @@ import { PrefKey, findInArray, CollectionSort, + UuidString, + NotesDisplayCriteria } from '@standardnotes/snjs'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { AppStateEvent } from '@/ui_models/app_state'; @@ -16,10 +18,6 @@ import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager'; import { PANEL_NAME_NOTES } from '@/views/constants'; -import { - notePassesFilter -} from './note_utils'; -import { UuidString } from '@standardnotes/snjs'; type NotesState = { panelTitle: string @@ -33,7 +31,13 @@ type NotesState = { hideNotePreview?: boolean hideDate?: boolean hideTags: boolean - noteFilter: { text: string } + noteFilter: { + text: string; + includeProtectedNoteText: boolean; + } + searchIsFocused: boolean; + searchOptionsAreFocused: boolean; + authorizingSearchOptions: boolean; mutable: { showMenu: boolean } completedFullSync: boolean [PrefKey.TagsPanelWidth]?: number @@ -72,6 +76,8 @@ class NotesViewCtrl extends PureViewCtrl { private searchKeyObserver: any private noteFlags: Partial> = {} private removeObservers: Array<() => void> = []; + private searchBarInput?: JQLite; + private searchOptionsInput?: JQLite; /* @ngInject */ constructor($timeout: ng.ITimeoutService,) { @@ -127,10 +133,16 @@ class NotesViewCtrl extends PureViewCtrl { renderedNotes: [], renderedNotesTags: [], mutable: { showMenu: false }, - noteFilter: { text: '' }, + noteFilter: { + text: '', + includeProtectedNoteText: false + }, panelTitle: '', completedFullSync: false, hideTags: true, + searchIsFocused: false, + searchOptionsAreFocused: false, + authorizingSearchOptions: false }; } @@ -211,7 +223,7 @@ class NotesViewCtrl extends PureViewCtrl { */ private async createPlaceholderNote() { const selectedTag = this.selectedTag!; - if (selectedTag.isSmartTag() && !selectedTag.isAllTag) { + if (selectedTag.isSmartTag && !selectedTag.isAllTag) { return; } return this.createNewNote(); @@ -330,20 +342,20 @@ class NotesViewCtrl extends PureViewCtrl { * an index is roughly O(n^2). */ private reloadNotesDisplayOptions() { - const tag = this.appState.selectedTag!; - this.application!.setNotesDisplayOptions( - tag, - this.state.sortBy! as CollectionSort, - this.state.sortReverse! ? 'asc' : 'dsc', - (note: SNNote) => { - return notePassesFilter( - note, - this.getState().showArchived! || tag?.isArchiveTag || tag?.isTrashTag, - this.getState().hidePinned!, - this.getState().noteFilter.text.toLowerCase() - ); + const tag = this.appState.selectedTag; + const searchText = this.getState().noteFilter.text.toLowerCase(); + const criteria = NotesDisplayCriteria.Create({ + sortProperty: this.state.sortBy! as CollectionSort, + sortDirection: this.state.sortReverse! ? 'asc' : 'dsc', + tags: tag ? [tag] : [], + includeArchived: this.getState().showArchived!, + includePinned: !this.getState().hidePinned!, + searchQuery: { + query: searchText ?? '', + includeProtectedNoteText: this.state.noteFilter.includeProtectedNoteText } - ); + }); + this.application!.setNotesDisplayCriteria(criteria); } private get selectedTag() { @@ -376,7 +388,7 @@ class NotesViewCtrl extends PureViewCtrl { const selectedTag = this.appState.selectedTag; if (!selectedTag) { return []; - } else if (selectedTag?.isSmartTag()) { + } else if (selectedTag?.isSmartTag) { return notes.map((note) => this.appState .getNoteTags(note) @@ -691,6 +703,64 @@ class NotesViewCtrl extends PureViewCtrl { await this.reloadNotes(); } + async onIncludeProtectedNoteTextChange(event: Event) { + this.searchBarInput?.[0].focus(); + if (this.state.noteFilter.includeProtectedNoteText) { + this.state.noteFilter.includeProtectedNoteText = false; + this.reloadNotesDisplayOptions(); + await this.reloadNotes(); + } else { + this.setState({ + authorizingSearchOptions: true, + }); + event.preventDefault(); + if (await this.application.authorizeSearchingProtectedNotesText()) { + this.state.noteFilter.includeProtectedNoteText = true; + this.reloadNotesDisplayOptions(); + await this.reloadNotes(); + } + await this.$timeout(50); + await this.setState({ + authorizingSearchOptions: false, + }); + } + } + + onSearchInputFocus() { + this.setState({ + searchIsFocused: true, + }); + } + + async onSearchInputBlur() { + await this.appState.mouseUp; + /** + * Wait a non-zero amount of time so the newly-focused element can have + * enough time to set its state + */ + await this.$timeout(50); + await this.setState({ + searchIsFocused: + this.searchBarInput?.[0] === document.activeElement, + }); + this.onFilterEnter(); + } + + onSearchOptionsFocus() { + this.setState({ + searchOptionsAreFocused: true, + }); + } + + async onSearchOptionsBlur() { + await this.appState.mouseUp; + await this.$timeout(50); + this.setState({ + searchOptionsAreFocused: + this.searchOptionsInput?.[0] === document.activeElement, + }); + } + onFilterEnter() { /** * For Desktop, performing a search right away causes diff --git a/app/assets/javascripts/views/tags/tags_view.ts b/app/assets/javascripts/views/tags/tags_view.ts index b4a609f59..d7ce1bbc8 100644 --- a/app/assets/javascripts/views/tags/tags_view.ts +++ b/app/assets/javascripts/views/tags/tags_view.ts @@ -180,7 +180,7 @@ class TagsViewCtrl extends PureViewCtrl { if (tag === this.state.templateTag) { continue; } - if (tag.isSmartTag()) { + if (tag.isSmartTag) { /** Other smart tags do not contain counts */ if (tag.isAllTag) { const notes = this.application.notesMatchingSmartTag(tag as SNSmartTag) @@ -246,12 +246,8 @@ class TagsViewCtrl extends PureViewCtrl { this.selectTag(tag as SNTag); } } else if (data.item!.content_type === ContentType.SmartTag) { - this.application.createTemplateItem( - ContentType.SmartTag, - data.item!.content as PayloadContent - ).then(smartTag => { - this.selectTag(smartTag as SNSmartTag); - }); + const matchingTag = this.getState().smartTags.find(t => t.uuid === data.item!.uuid); + this.selectTag(matchingTag as SNSmartTag); } } else if (action === ComponentAction.ClearSelection) { this.selectTag(this.getState().smartTags[0]); diff --git a/app/assets/stylesheets/_main.scss b/app/assets/stylesheets/_main.scss index 914c0bd12..7503712c6 100644 --- a/app/assets/stylesheets/_main.scss +++ b/app/assets/stylesheets/_main.scss @@ -206,10 +206,6 @@ $footer-height: 32px; .section-title-bar { - .padded { - padding: 0 14px; - } - .add-button { font-size: 12px; } diff --git a/app/assets/stylesheets/_notes.scss b/app/assets/stylesheets/_notes.scss index fa7d78c10..02a818464 100644 --- a/app/assets/stylesheets/_notes.scss +++ b/app/assets/stylesheets/_notes.scss @@ -42,7 +42,6 @@ #notes-menu-bar { position: relative; - margin-top: 14px; } #notes-options-menu { diff --git a/app/assets/stylesheets/_ui.scss b/app/assets/stylesheets/_ui.scss index 8ee59b19d..2b44ab40f 100644 --- a/app/assets/stylesheets/_ui.scss +++ b/app/assets/stylesheets/_ui.scss @@ -97,13 +97,37 @@ $screen-md-max: ($screen-lg-min - 1) !default; justify-self: flex-start; } +.animate-slide-in-top { + animation: slide-in-top .1s ease-out; +} + +@keyframes slide-in-top { + 0% { + opacity: 0; + transform: translateY(-40%); + } + 75% { + opacity: 1; + } + 100% { + transform: translateY(0%); + } +} + .m-0 { margin: 0; } +.mb-0 { + margin-bottom: 0; +} + .mt-1 { margin-top: .25rem; } +.mt-2 { + margin-top: .5rem; +} .mt-3 { margin-top: .75rem; } @@ -114,10 +138,17 @@ $screen-md-max: ($screen-lg-min - 1) !default; .p-0 { padding: 0rem; } +.p-4 { + padding: 1rem; +} .p-5 { padding: 1.25rem; } +.pt-0 { + padding-top: 0; +} + .px-3 { padding-left: .75rem; padding-right: .75rem; diff --git a/app/assets/templates/directives/account-menu.pug b/app/assets/templates/directives/account-menu.pug index e56af6939..bde3c0f21 100644 --- a/app/assets/templates/directives/account-menu.pug +++ b/app/assets/templates/directives/account-menu.pug @@ -149,7 +149,7 @@ .sk-panel-row .sk-panel-column .sk-h1.sk-bold.wrap {{self.state.user.email}} - .sk-subtitle.subtle.normal {{self.state.server}} + .sk-subtitle.neutral {{self.state.server}} .sk-panel-row a.sk-a.info.sk-panel-row.condensed( ng-click="self.openPasswordWizard()" @@ -303,11 +303,11 @@ .sk-panel-footer .sk-panel-row .sk-p.left.neutral - span.faded {{self.state.appVersion}} + span {{self.state.appVersion}} span(ng-if="self.state.showBetaWarning") - span.faded ( + span ( a.sk-a(ng-click="self.appState.disableBetaWarning()") Hide beta warning - span.faded ) + span ) a.sk-a.right( ng-click='self.hidePasswordForm()', ng-if='self.state.formData.showLogin || self.state.formData.showRegister' diff --git a/package.json b/package.json index 9c97e8fa5..6a94b0981 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "standard-notes-web", - "version": "3.6.1", + "version": "3.6.2", "license": "AGPL-3.0-or-later", "repository": { "type": "git", @@ -53,7 +53,7 @@ "pug-loader": "^2.4.0", "sass-loader": "^8.0.2", "serve-static": "^1.14.1", - "sn-stylekit": "^2.2.0", + "sn-stylekit": "^2.2.1", "ts-loader": "^8.0.17", "typescript": "^4.1.5", "typescript-eslint": "0.0.1-alpha.0", @@ -68,7 +68,7 @@ "@reach/alert-dialog": "^0.13.0", "@reach/dialog": "^0.13.0", "@standardnotes/sncrypto-web": "^1.2.10", - "@standardnotes/snjs": "^2.0.61", + "@standardnotes/snjs": "^2.0.67", "mobx": "^6.1.6", "preact": "^10.5.12" } diff --git a/yarn.lock b/yarn.lock index bfe148027..e12a45984 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1832,6 +1832,11 @@ dependencies: tslib "^2.0.0" +"@standardnotes/auth@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-2.0.0.tgz#93f633fd40855f87843f911109e92b29dcbc5a04" + integrity sha512-B2NznCm3pzwBvBU/YQfuDrtlEbLO3hNH3QrqSwK2dFwUGAnl5UQPC9FKFWYgly05rWevwMY3IUmiZRzUEVlKsQ== + "@standardnotes/sncrypto-common@^1.2.7", "@standardnotes/sncrypto-common@^1.2.9": version "1.2.9" resolved "https://registry.yarnpkg.com/@standardnotes/sncrypto-common/-/sncrypto-common-1.2.9.tgz#5212a959e4ec563584e42480bfd39ef129c3cbdf" @@ -1845,11 +1850,12 @@ "@standardnotes/sncrypto-common" "^1.2.7" libsodium-wrappers "^0.7.8" -"@standardnotes/snjs@^2.0.61": - version "2.0.61" - resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.61.tgz#82608ef48830a80afbc9468ed2d308e59e4e9a08" - integrity sha512-MVnzT7cX7oIak9g/xlOJKfWCfBygw8kXtuVHSofdxPhYIPIr1MwK4xkkqho/ZShVfPQEESyj3RaEsOIzKuuL4w== +"@standardnotes/snjs@^2.0.67": + version "2.0.67" + resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.67.tgz#87e29f40bb5efaa36f30ddc5905164f7dce194d9" + integrity sha512-XCDxlFQCh0zmV3Hc9mjU7ritZ/2Ma5JPoCbDy4CIAlkKdmVL4tu/4jCfRFILM0zpKF/kLsCTbLGdG7TgU/ReKg== dependencies: + "@standardnotes/auth" "^2.0.0" "@standardnotes/sncrypto-common" "^1.2.9" "@svgr/babel-plugin-add-jsx-attribute@^5.4.0": @@ -7802,10 +7808,10 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -sn-stylekit@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-2.2.0.tgz#0c143cb25addf4a1edf02d41ea1d6821017f8652" - integrity sha512-xUx+EujROZBaoMrvM0y7CjHPD6WJfwu1tY3oeFG/sV3M9YvCcGkX76Jz9iPt0daeYM4h0oAWphniH7pLi3aFwA== +sn-stylekit@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-2.2.1.tgz#4a81a05b2b2d67a11af9d06f3964ac1ece3aa84a" + integrity sha512-mrvUbf2HbWOfxbNglo7RXa5JBe9UV9rurupeGoX/Kh4/lVlB2n/56ZT103xk/4tp0/mVCpxjD7Dt1stKFxsvFA== snapdragon-node@^2.0.1: version "2.1.1"