Merge branch 'release/3.5.12'

This commit is contained in:
Baptiste Grob
2020-12-29 12:58:15 +01:00
37 changed files with 8862 additions and 13528 deletions

View File

@@ -1,10 +1,13 @@
{
"presets": [
"@babel/typescript",
"@babel/preset-typescript",
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"angularjs-annotate"
"angularjs-annotate",
["@babel/plugin-transform-react-jsx", {
"pragma": "h",
"pragmaFrag": "Fragment"
}]
]
}

View File

@@ -17,10 +17,10 @@ jobs:
uses: actions/checkout@v2
- name: Install dependencies
run: npm ci
run: yarn install --pure-lockfile
- name: Typescript
run: npm run tsc
run: yarn tsc
deploy:

View File

@@ -17,10 +17,10 @@ jobs:
uses: actions/checkout@v2
- name: Install dependencies
run: npm ci
run: yarn install --pure-lockfile
- name: Typescript
run: npm run tsc
run: yarn tsc
deploy:

View File

@@ -13,6 +13,6 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: npm ci
run: yarn install --pure-lockfile
- name: Typescript
run: npm run tsc
run: yarn tsc

View File

@@ -18,10 +18,10 @@ jobs:
uses: actions/checkout@v2
- name: Install dependencies
run: npm ci
run: yarn install --pure-lockfile
- name: Typescript
run: npm run tsc
run: yarn tsc
deploy:

4
.gitignore vendored
View File

@@ -42,3 +42,7 @@ dump.rdb
/dist/stylesheets
/dist/fonts
/dist/@types
# Yarn
yarn-error.log
package-lock.json

View File

@@ -1,4 +1,4 @@
FROM ruby:2.7.1-alpine
FROM ruby:2.7.1-alpine3.12
ARG UID=1000
ARG GID=1000
@@ -7,10 +7,11 @@ RUN addgroup -S webapp -g $GID && adduser -D -S webapp -G webapp -u $UID
RUN apk add --update --no-cache \
alpine-sdk \
nodejs \
nodejs-current \
python2 \
git \
nodejs-npm \
yarn \
tzdata
WORKDIR /app/
@@ -19,17 +20,17 @@ RUN chown -R $UID:$GID .
USER webapp
COPY --chown=$UID:$GID package.json package-lock.json Gemfile Gemfile.lock /app/
COPY --chown=$UID:$GID package.json yarn.lock Gemfile Gemfile.lock /app/
COPY --chown=$UID:$GID vendor /app/vendor
RUN npm ci
RUN yarn install --pure-lockfile
RUN gem install bundler && bundle install
COPY --chown=$UID:$GID . /app/
RUN npm run bundle
RUN yarn bundle
RUN bundle exec rails assets:precompile

View File

@@ -88,9 +88,10 @@ This repo contains the core code used in the web app, as well as the Electron-ba
**Instructions:**
1. Ensure you have [Yarn](https://classic.yarnpkg.com) installed
1. Clone the repo
1. `npm run setup`
1. `npm start`
1. `yarn setup`
1. `yarn start`
Then open your browser to `http://localhost:3001`.

View File

@@ -55,9 +55,9 @@ import { trusted } from './filters';
import { isDev } from './utils';
import { BrowserBridge } from './services/browserBridge';
import { startErrorReporting } from './services/errorReporting';
import { alertDialog } from './services/alertService';
import { StartApplication } from './startApplication';
import { Bridge } from './services/bridge';
import { SessionsModalDirective } from './directives/views/sessionsModal';
const startApplication: StartApplication = async function startApplication(
defaultSyncServerHost: string,
@@ -122,7 +122,8 @@ const startApplication: StartApplication = async function startApplication(
)
.directive('revisionPreviewModal', () => new RevisionPreviewModal())
.directive('historyMenu', () => new HistoryMenu())
.directive('syncResolutionMenu', () => new SyncResolutionMenu());
.directive('syncResolutionMenu', () => new SyncResolutionMenu())
.directive('sessionsModal', SessionsModalDirective);
// Filters
angular.module('app').filter('trusted', ['$sce', trusted]);

View File

@@ -69,6 +69,7 @@ type AccountMenuState = {
syncInProgress: boolean;
syncError: string;
syncPercentage: string;
showSessions: boolean;
}
class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
@@ -101,6 +102,7 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
mutable: {},
showBetaWarning: false,
errorReportingEnabled: !storage.get(StorageKey.DisableErrorReporting),
showSessions: false,
} as AccountMenuState;
}
@@ -130,8 +132,12 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
};
}
$onInit() {
async $onInit() {
super.$onInit();
this.setState({
showSessions: this.appState.enableUnfinishedFeatures && await this.application.userCanManageSessions()
});
const sync = this.appState.sync;
this.removeSyncObserver = autorun(() => {
this.setState({
@@ -320,6 +326,11 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
this.application!.presentPasswordWizard(PasswordWizardType.ChangePassword);
}
openSessionsModal() {
this.close();
this.appState.openSessionsModal();
}
async openPrivilegesModal() {
const run = () => {
this.application!.presentPrivilegesManagementModal();

View File

@@ -0,0 +1,252 @@
import { AppState } from '@/ui_models/app_state';
import { PureViewCtrl } from '@/views';
import { SNApplication, RemoteSession, UuidString } from '@standardnotes/snjs';
import { autorun, IAutorunOptions, IReactionPublic } from 'mobx';
import { render, FunctionComponent } from 'preact';
import { useState, useEffect, useRef } from 'preact/hooks';
import { Dialog } from '@reach/dialog';
import { Alert } from '@reach/alert';
import {
AlertDialog,
AlertDialogDescription,
AlertDialogLabel,
} from '@reach/alert-dialog';
function useAutorun(view: (r: IReactionPublic) => any, opts?: IAutorunOptions) {
useEffect(() => autorun(view, opts), []);
}
function useSessions(
application: SNApplication
): [
RemoteSession[],
() => void,
boolean,
(uuid: UuidString) => Promise<void>,
string
] {
const [sessions, setSessions] = useState<RemoteSession[]>([]);
const [lastRefreshDate, setLastRefreshDate] = useState(Date.now());
const [refreshing, setRefreshing] = useState(true);
const [errorMessage, setErrorMessage] = useState('');
useEffect(() => {
(async () => {
setRefreshing(true);
const response = await application.getSessions();
if ('error' in response) {
if (response.error?.message) {
setErrorMessage(response.error.message);
} else {
setErrorMessage('An unknown error occured while loading sessions.');
}
} else {
const sessions = response as RemoteSession[];
setSessions(sessions);
setErrorMessage('');
}
setRefreshing(false);
})();
}, [lastRefreshDate]);
function refresh() {
setLastRefreshDate(Date.now());
}
async function revokeSession(uuid: UuidString) {
let sessionsBeforeRevoke = sessions;
setSessions(sessions.filter((session) => session.uuid !== uuid));
const response = await application.revokeSession(uuid);
if ('error' in response) {
if (response.error?.message) {
setErrorMessage(response.error?.message);
} else {
setErrorMessage('An unknown error occured while revoking the session.');
}
setSessions(sessionsBeforeRevoke);
}
}
return [sessions, refresh, refreshing, revokeSession, errorMessage];
}
const SessionsModal: FunctionComponent<{
appState: AppState;
application: SNApplication;
}> = ({ appState, application }) => {
const close = () => appState.closeSessionsModal();
const [
sessions,
refresh,
refreshing,
revokeSession,
errorMessage,
] = useSessions(application);
const [revokingSessionUuid, setRevokingSessionUuid] = useState('');
const closeRevokeSessionAlert = () => setRevokingSessionUuid('');
const cancelRevokeRef = useRef<HTMLButtonElement>();
const formatter = new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
hour: 'numeric',
minute: 'numeric',
});
return (
<>
<Dialog onDismiss={close}>
<div className="sk-modal-content sessions-modal">
<div class="sn-component">
<div class="sk-panel">
<div class="sk-panel-header">
<div class="sk-panel-header-title">Active Sessions</div>
<div className="buttons">
<button
class="sk-a close-button info"
disabled={refreshing}
onClick={refresh}
>
Refresh
</button>
<button class="sk-a close-button info" onClick={close}>
Close
</button>
</div>
</div>
<div class="sk-panel-content">
{refreshing ? (
<>
<div class="sk-spinner small info"></div>
<h2 className="sk-p sessions-modal-refreshing">
Loading sessions
</h2>
</>
) : (
<>
{errorMessage && (
<Alert className="sk-p bold">{errorMessage}</Alert>
)}
{sessions.length > 0 && (
<ul>
{sessions.map((session) => (
<li>
<h2>{session.device_info}</h2>
{session.current ? (
<span className="info bold">Current session</span>
) : (
<>
<p>
Signed in on{' '}
{formatter.format(session.updated_at)}
</p>
<button
className="sk-button danger sk-label"
onClick={() =>
setRevokingSessionUuid(session.uuid)
}
>
<span>Revoke</span>
</button>
</>
)}
</li>
))}
</ul>
)}
</>
)}
</div>
</div>
</div>
</div>
</Dialog>
{revokingSessionUuid && (
<AlertDialog leastDestructiveRef={cancelRevokeRef}>
<div className="sk-modal-content">
<div className="sn-component">
<div className="sk-panel">
<div className="sk-panel-content">
<div className="sk-panel-section">
<AlertDialogLabel className="sk-h3 sk-panel-section-title">
Revoke this session?
</AlertDialogLabel>
<AlertDialogDescription className="sk-panel-row">
<p>
The associated app will be signed out and all data
removed from the device when it is next launched. You
can sign back in on that device at any time.
</p>
</AlertDialogDescription>
<div className="sk-panel-row">
<div className="sk-button-group">
<button
className="sk-button neutral sk-label"
ref={cancelRevokeRef}
onClick={closeRevokeSessionAlert}
>
<span>Cancel</span>
</button>
<button
className="sk-button danger sk-label"
onClick={() => {
closeRevokeSessionAlert();
revokeSession(revokingSessionUuid);
}}
>
<span>Revoke</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</AlertDialog>
)}
</>
);
};
const Sessions: FunctionComponent<{
appState: AppState;
application: SNApplication;
}> = ({ appState, application }) => {
const [showModal, setShowModal] = useState(false);
useAutorun(() => setShowModal(appState.isSessionsModalVisible));
if (showModal) {
return <SessionsModal application={application} appState={appState} />;
} else {
return null;
}
};
class SessionsModalCtrl extends PureViewCtrl<{}, {}> {
/* @ngInject */
constructor(private $element: JQLite, $timeout: ng.ITimeoutService) {
super($timeout);
this.$element = $element;
}
$onChanges() {
render(
<Sessions appState={this.appState} application={this.application} />,
this.$element[0]
);
}
}
export function SessionsModalDirective() {
return {
controller: SessionsModalCtrl,
bindToController: true,
scope: {
application: '=',
},
};
}

View File

@@ -1,8 +1,10 @@
//= require_tree ./app
// css
import '@reach/dialog/styles.css';
import 'sn-stylekit/dist/stylekit.css';
import '../stylesheets/index.css.scss';
// import '../stylesheets/_reach-sub.scss';
// Vendor
import 'angular';

View File

@@ -87,7 +87,7 @@ export class ArchiveManager {
scriptTag.async = false;
const headTag = document.getElementsByTagName('head')[0];
headTag.appendChild(scriptTag);
return new Promise((resolve) => {
return new Promise<void>((resolve) => {
scriptTag.onload = () => {
this.zip.workerScriptsPath = 'assets/zip/';
resolve();

View File

@@ -4,6 +4,5 @@ export { DesktopManager } from './desktopManager';
export { KeyboardManager } from './keyboardManager';
export { AutolockService } from './autolock_service';
export { NativeExtManager } from './nativeExtManager';
export { PreferencesManager } from './preferencesManager';
export { StatusManager } from './statusManager';
export { ThemeManager } from './themeManager';

View File

@@ -2,12 +2,16 @@ export enum StorageKey {
DisableErrorReporting = 'DisableErrorReporting',
}
export type StorageValue = {
[StorageKey.DisableErrorReporting]: boolean;
}
export const storage = {
get(key: StorageKey) {
get<K extends StorageKey>(key: K): StorageValue[K] | null {
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : null;
},
set(key: StorageKey, value: unknown) {
set<K extends StorageKey>(key: K, value: StorageValue[K]) {
localStorage.setItem(key, JSON.stringify(value));
},
remove(key: StorageKey) {

View File

@@ -1,99 +0,0 @@
import { WebApplication } from '@/ui_models/application';
import {
SNPredicate,
ContentType,
ApplicationService,
SNUserPrefs,
WebPrefKey,
UserPrefsMutator,
FillItemContent,
ApplicationEvent,
} from '@standardnotes/snjs';
export class PreferencesManager extends ApplicationService {
private userPreferences!: SNUserPrefs;
private loadingPrefs = false;
private unubscribeStreamItems?: () => void;
private needsSingletonReload = true;
/** @override */
async onAppLaunch() {
super.onAppLaunch();
this.reloadSingleton();
this.streamPreferences();
}
async onAppEvent(event: ApplicationEvent) {
super.onAppEvent(event);
if (event === ApplicationEvent.CompletedFullSync) {
this.reloadSingleton();
}
}
deinit() {
this.unubscribeStreamItems?.();
}
get webApplication() {
return this.application as WebApplication;
}
streamPreferences() {
this.unubscribeStreamItems = this.application!.streamItems(
ContentType.UserPrefs,
() => {
this.needsSingletonReload = true;
}
);
}
private async reloadSingleton() {
if (this.loadingPrefs || !this.needsSingletonReload) {
return;
}
this.loadingPrefs = true;
const contentType = ContentType.UserPrefs;
const predicate = new SNPredicate('content_type', '=', contentType);
const previousRef = this.userPreferences;
this.userPreferences = (await this.application!.singletonManager!.findOrCreateSingleton(
predicate,
contentType,
FillItemContent({})
)) as SNUserPrefs;
this.loadingPrefs = false;
this.needsSingletonReload = false;
if (
previousRef?.uuid !== this.userPreferences.uuid ||
this.userPreferences.lastSyncBegan?.getTime() !==
previousRef?.lastSyncBegan?.getTime()
) {
this.webApplication
.getAppState()
.setUserPreferences(this.userPreferences);
}
}
syncUserPreferences() {
if (this.userPreferences) {
this.application!.saveItem(this.userPreferences.uuid);
}
}
getValue(key: WebPrefKey, defaultValue?: any) {
if (!this.userPreferences) {
return defaultValue;
}
const value = this.userPreferences.getPref(key);
return value !== undefined && value !== null ? value : defaultValue;
}
async setUserPrefValue(key: WebPrefKey, value: any, sync = false) {
await this.application!.changeItem(this.userPreferences.uuid, (m) => {
const mutator = m as UserPrefsMutator;
mutator.setWebPref(key, value);
});
if (sync) {
this.syncUserPreferences();
}
}
}

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "esnext",
"target": "ES2019",
"module": "commonjs",
"moduleResolution": "node",
"allowJs": true,
@@ -12,6 +12,8 @@
"newLine": "lf",
"declarationDir": "../../../dist/@types",
"baseUrl": ".",
"jsx": "react-jsx",
"jsxImportSource": "preact",
"paths": {
"%/*": ["../templates/*"],
"@/*": ["./*"],

View File

@@ -1,4 +1,4 @@
import { isDesktopApplication } from '@/utils';
import { isDesktopApplication, isDev } from '@/utils';
import pull from 'lodash/pull';
import {
ProtectedAction,
@@ -11,7 +11,7 @@ import {
PayloadSource,
DeinitSource,
UuidString,
SyncOpStatus
SyncOpStatus,
} from '@standardnotes/snjs';
import { WebApplication } from '@/ui_models/application';
import { Editor } from '@/ui_models/editor';
@@ -19,23 +19,22 @@ import { action, makeObservable, observable } from 'mobx';
import { Bridge } from '@/services/bridge';
export enum AppStateEvent {
TagChanged = 1,
ActiveEditorChanged = 2,
PreferencesChanged = 3,
PanelResized = 4,
EditorFocused = 5,
BeganBackupDownload = 6,
EndedBackupDownload = 7,
WindowDidFocus = 9,
WindowDidBlur = 10,
};
TagChanged,
ActiveEditorChanged,
PanelResized,
EditorFocused,
BeganBackupDownload,
EndedBackupDownload,
WindowDidFocus,
WindowDidBlur,
}
export enum EventSource {
UserInteraction = 1,
Script = 2
};
UserInteraction,
Script,
}
type ObserverCallback = (event: AppStateEvent, data?: any) => Promise<void>
type ObserverCallback = (event: AppStateEvent, data?: any) => Promise<void>;
const SHOW_BETA_WARNING_KEY = 'show_beta_warning';
@@ -61,8 +60,8 @@ class ActionsMenuState {
export class SyncState {
inProgress = false;
errorMessage?: string;
humanReadablePercentage?: string;
errorMessage?: string = undefined;
humanReadablePercentage?: string = undefined;
constructor() {
makeObservable(this, {
@@ -77,7 +76,8 @@ export class SyncState {
this.errorMessage = status.error?.message;
this.inProgress = status.syncInProgress;
const stats = status.getStats();
const completionPercentage = stats.uploadCompletionCount === 0
const completionPercentage =
stats.uploadCompletionCount === 0
? 0
: stats.uploadCompletionCount / stats.uploadTotalCount;
@@ -93,6 +93,9 @@ export class SyncState {
}
export class AppState {
readonly enableUnfinishedFeatures =
isDev || location.host.includes('app-dev.standardnotes.org');
$rootScope: ng.IRootScopeService;
$timeout: ng.ITimeoutService;
application: WebApplication;
@@ -103,36 +106,40 @@ export class AppState {
rootScopeCleanup2: any;
onVisibilityChange: any;
selectedTag?: SNTag;
userPreferences?: SNUserPrefs;
multiEditorEnabled = false;
showBetaWarning = false;
readonly actionsMenu = new ActionsMenuState();
readonly sync = new SyncState();
isSessionsModalVisible = false;
/* @ngInject */
constructor(
$rootScope: ng.IRootScopeService,
$timeout: ng.ITimeoutService,
application: WebApplication,
private bridge: Bridge,
private bridge: Bridge
) {
this.$timeout = $timeout;
this.$rootScope = $rootScope;
this.application = application;
makeObservable(this, {
showBetaWarning: observable,
isSessionsModalVisible: observable,
enableBetaWarning: action,
disableBetaWarning: action,
openSessionsModal: action,
closeSessionsModal: action,
});
this.addAppEventObserver();
this.streamNotesAndTags();
this.onVisibilityChange = () => {
const visible = document.visibilityState === "visible";
const visible = document.visibilityState === 'visible';
const event = visible
? AppStateEvent.WindowDidFocus
: AppStateEvent.WindowDidBlur;
this.notifyEvent(event);
}
};
this.registerVisibilityObservers();
this.determineBetaWarningValue();
}
@@ -155,6 +162,14 @@ export class AppState {
this.onVisibilityChange = undefined;
}
openSessionsModal() {
this.isSessionsModalVisible = true;
}
closeSessionsModal() {
this.isSessionsModalVisible = false;
}
disableBetaWarning() {
this.showBetaWarning = false;
localStorage.setItem(SHOW_BETA_WARNING_KEY, 'false');
@@ -190,10 +205,10 @@ export class AppState {
async createEditor(title?: string) {
const activeEditor = this.getActiveEditor();
const activeTagUuid = this.selectedTag
? this.selectedTag.isSmartTag()
? undefined
: this.selectedTag.uuid
: undefined;
? this.selectedTag.isSmartTag()
? undefined
: this.selectedTag.uuid
: undefined;
if (!activeEditor || this.multiEditorEnabled) {
this.application.editorGroup.createEditor(
@@ -218,10 +233,13 @@ export class AppState {
}
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
};
if (note && note.safeContent.protected &&
await this.application.privilegesService!.actionRequiresPrivilege(
if (
note &&
note.safeContent.protected &&
(await this.application.privilegesService!.actionRequiresPrivilege(
ProtectedAction.ViewProtectedNotes
)) {
))
) {
return new Promise((resolve) => {
this.application.presentPrivilegesModal(
ProtectedAction.ViewProtectedNotes,
@@ -269,8 +287,8 @@ export class AppState {
async (items, source) => {
/** Close any editors for deleted/trashed/archived notes */
if (source === PayloadSource.PreSyncSave) {
const notes = items.filter((candidate) =>
candidate.content_type === ContentType.Note
const notes = items.filter(
(candidate) => candidate.content_type === ContentType.Note
) as SNNote[];
for (const note of notes) {
const editor = this.editorForNote(note);
@@ -287,7 +305,9 @@ export class AppState {
}
}
if (this.selectedTag) {
const matchingTag = items.find((candidate) => candidate.uuid === this.selectedTag!.uuid);
const matchingTag = items.find(
(candidate) => candidate.uuid === this.selectedTag!.uuid
);
if (matchingTag) {
this.selectedTag = matchingTag as SNTag;
}
@@ -321,9 +341,12 @@ export class AppState {
this.rootScopeCleanup1 = this.$rootScope.$on('window-lost-focus', () => {
this.notifyEvent(AppStateEvent.WindowDidBlur);
});
this.rootScopeCleanup2 = this.$rootScope.$on('window-gained-focus', () => {
this.notifyEvent(AppStateEvent.WindowDidFocus);
});
this.rootScopeCleanup2 = this.$rootScope.$on(
'window-gained-focus',
() => {
this.notifyEvent(AppStateEvent.WindowDidFocus);
}
);
} else {
/* Tab visibility listener, web only */
document.addEventListener('visibilitychange', this.onVisibilityChange);
@@ -343,7 +366,7 @@ export class AppState {
* Timeout is particullary important so we can give all initial
* controllers a chance to construct before propogting any events *
*/
return new Promise((resolve) => {
return new Promise<void>((resolve) => {
this.$timeout(async () => {
for (const callback of this.observers) {
await callback(eventName, data);
@@ -359,71 +382,39 @@ export class AppState {
}
const previousTag = this.selectedTag;
this.selectedTag = tag;
this.notifyEvent(
AppStateEvent.TagChanged,
{
tag: tag,
previousTag: previousTag
}
);
this.notifyEvent(AppStateEvent.TagChanged, {
tag: tag,
previousTag: previousTag,
});
}
/** Returns the tags that are referncing this note */
public getNoteTags(note: SNNote) {
return this.application.referencingForItem(note).filter((ref) => {
return ref.content_type === ContentType.Tag;
}) as SNTag[]
}
/** Returns the notes this tag references */
public getTagNotes(tag: SNTag) {
if (tag.isSmartTag()) {
return this.application.notesMatchingSmartTag(tag as SNSmartTag);
} else {
return this.application.referencesForItem(tag).filter((ref) => {
return ref.content_type === ContentType.Note;
}) as SNNote[]
}
}) as SNTag[];
}
public getSelectedTag() {
return this.selectedTag;
}
setUserPreferences(preferences: SNUserPrefs) {
this.userPreferences = preferences;
this.notifyEvent(
AppStateEvent.PreferencesChanged
);
}
panelDidResize(name: string, collapsed: boolean) {
this.notifyEvent(
AppStateEvent.PanelResized,
{
panel: name,
collapsed: collapsed
}
);
this.notifyEvent(AppStateEvent.PanelResized, {
panel: name,
collapsed: collapsed,
});
}
editorDidFocus(eventSource: EventSource) {
this.notifyEvent(
AppStateEvent.EditorFocused,
{ eventSource: eventSource }
);
this.notifyEvent(AppStateEvent.EditorFocused, { eventSource: eventSource });
}
beganBackupDownload() {
this.notifyEvent(
AppStateEvent.BeganBackupDownload
);
this.notifyEvent(AppStateEvent.BeganBackupDownload);
}
endedBackupDownload(success: boolean) {
this.notifyEvent(
AppStateEvent.EndedBackupDownload,
{ success: success }
);
this.notifyEvent(AppStateEvent.EndedBackupDownload, { success: success });
}
}

View File

@@ -22,7 +22,6 @@ import {
NativeExtManager,
StatusManager,
ThemeManager,
PreferencesManager,
KeyboardManager
} from '@/services';
import { AppState } from '@/ui_models/app_state';
@@ -38,7 +37,6 @@ type WebServices = {
nativeExtService: NativeExtManager
statusManager: StatusManager
themeService: ThemeManager
prefsService: PreferencesManager
keyboardService: KeyboardManager
}
@@ -141,10 +139,6 @@ export class WebApplication extends SNApplication {
return this.webServices.themeService;
}
public getPrefsService() {
return this.webServices.prefsService;
}
public getKeyboardService() {
return this.webServices.keyboardService;
}

View File

@@ -7,7 +7,6 @@ import {
KeyboardManager,
AutolockService,
NativeExtManager,
PreferencesManager,
StatusManager,
ThemeManager
} from '@/services';
@@ -82,9 +81,6 @@ export class ApplicationGroup extends SNApplicationGroup {
const nativeExtService = new NativeExtManager(
application
);
const prefsService = new PreferencesManager(
application
);
const statusService = new StatusManager();
const themeService = new ThemeManager(
application,
@@ -96,7 +92,6 @@ export class ApplicationGroup extends SNApplicationGroup {
keyboardService,
autolockService,
nativeExtService,
prefsService,
statusManager: statusService,
themeService
});

View File

@@ -69,7 +69,7 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
if (!this.$timeout) {
return;
}
return new Promise((resolve) => {
return new Promise<void>((resolve) => {
this.stateTimeout = this.$timeout(() => {
/**
* State changes must be *inside* the timeout block for them to be affected in the UI

View File

@@ -24,3 +24,6 @@
symbol#layers-sharp.ionicon(viewbox="0 0 512 512")
path(d="M480 150L256 48 32 150l224 104 224-104zM255.71 392.95l-144.81-66.2L32 362l224 102 224-102-78.69-35.3-145.6 66.25z")
path(d="M480 256l-75.53-33.53L256.1 290.6l-148.77-68.17L32 256l224 102 224-102z")
sessions-modal(
application='self.application'
)

View File

@@ -16,7 +16,7 @@ import {
Uuids,
ComponentArea,
ComponentAction,
WebPrefKey,
PrefKey,
ComponentMutator,
} from '@standardnotes/snjs';
import find from 'lodash/find';
@@ -135,9 +135,9 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
onReady: () => this.reloadPreferences()
};
/** Used by .pug template */
this.prefKeyMonospace = WebPrefKey.EditorMonospaceEnabled;
this.prefKeySpellcheck = WebPrefKey.EditorSpellcheck;
this.prefKeyMarginResizers = WebPrefKey.EditorResizersEnabled;
this.prefKeyMonospace = PrefKey.EditorMonospaceEnabled;
this.prefKeySpellcheck = PrefKey.EditorSpellcheck;
this.prefKeyMarginResizers = PrefKey.EditorResizersEnabled;
this.editorMenuOnSelect = this.editorMenuOnSelect.bind(this);
this.onPanelResizeFinish = this.onPanelResizeFinish.bind(this);
@@ -239,38 +239,39 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
this.registerComponentHandler();
}
/** @override */
onAppStateEvent(eventName: AppStateEvent, data: any) {
if (eventName === AppStateEvent.PreferencesChanged) {
this.reloadPreferences();
}
}
/** @override */
onAppEvent(eventName: ApplicationEvent) {
if (eventName === ApplicationEvent.HighLatencySync) {
this.setState({ syncTakingTooLong: true });
} else if (eventName === ApplicationEvent.CompletedFullSync) {
this.setState({ syncTakingTooLong: false });
const isInErrorState = this.state.saveError;
/** if we're still dirty, don't change status, a sync is likely upcoming. */
if (!this.note.dirty && isInErrorState) {
this.showAllChangesSavedStatus();
}
} else if (eventName === ApplicationEvent.FailedSync) {
/**
* Only show error status in editor if the note is dirty.
* Otherwise, it means the originating sync came from somewhere else
* and we don't want to display an error here.
*/
if (this.note.dirty) {
this.showErrorStatus();
}
} else if (eventName === ApplicationEvent.LocalDatabaseWriteError) {
this.showErrorStatus({
message: "Offline Saving Issue",
desc: "Changes not saved"
});
switch (eventName) {
case ApplicationEvent.PreferencesChanged:
this.reloadPreferences();
break;
case ApplicationEvent.HighLatencySync:
this.setState({ syncTakingTooLong: true });
break;
case ApplicationEvent.CompletedFullSync:
this.setState({ syncTakingTooLong: false });
const isInErrorState = this.state.saveError;
/** if we're still dirty, don't change status, a sync is likely upcoming. */
if (!this.note.dirty && isInErrorState) {
this.showAllChangesSavedStatus();
}
break;
case ApplicationEvent.FailedSync:
/**
* Only show error status in editor if the note is dirty.
* Otherwise, it means the originating sync came from somewhere else
* and we don't want to display an error here.
*/
if (this.note.dirty) {
this.showErrorStatus();
}
break;
case ApplicationEvent.LocalDatabaseWriteError:
this.showErrorStatus({
message: "Offline Saving Issue",
desc: "Changes not saved"
});
break;
}
}
@@ -891,40 +892,40 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
async onPanelResizeFinish(width: number, left: number, isMaxWidth: boolean) {
if (isMaxWidth) {
await this.application.getPrefsService().setUserPrefValue(
WebPrefKey.EditorWidth,
await this.application.setPreference(
PrefKey.EditorWidth,
null
);
} else {
if (width !== undefined && width !== null) {
await this.application.getPrefsService().setUserPrefValue(
WebPrefKey.EditorWidth,
await this.application.setPreference(
PrefKey.EditorWidth,
width
);
this.leftPanelPuppet!.setWidth!(width);
}
}
if (left !== undefined && left !== null) {
await this.application.getPrefsService().setUserPrefValue(
WebPrefKey.EditorLeft,
await this.application.setPreference(
PrefKey.EditorLeft,
left
);
this.rightPanelPuppet!.setLeft!(left);
}
this.application.getPrefsService().syncUserPreferences();
this.application.sync();
}
async reloadPreferences() {
const monospaceFont = this.application.getPrefsService().getValue(
WebPrefKey.EditorMonospaceEnabled,
const monospaceFont = this.application.getPreference(
PrefKey.EditorMonospaceEnabled,
true
);
const spellcheck = this.application.getPrefsService().getValue(
WebPrefKey.EditorSpellcheck,
const spellcheck = this.application.getPreference(
PrefKey.EditorSpellcheck,
true
);
const marginResizersEnabled = this.application.getPrefsService().getValue(
WebPrefKey.EditorResizersEnabled,
const marginResizersEnabled = this.application.getPreference(
PrefKey.EditorResizersEnabled,
true
);
await this.setState({
@@ -945,16 +946,16 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
this.leftPanelPuppet?.ready &&
this.rightPanelPuppet?.ready
) {
const width = this.application.getPrefsService().getValue(
WebPrefKey.EditorWidth,
const width = this.application.getPreference(
PrefKey.EditorWidth,
null
);
if (width != null) {
this.leftPanelPuppet!.setWidth!(width);
this.rightPanelPuppet!.setWidth!(width);
}
const left = this.application.getPrefsService().getValue(
WebPrefKey.EditorLeft,
const left = this.application.getPreference(
PrefKey.EditorLeft,
null
);
if (left != null) {
@@ -982,24 +983,23 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
}
}
async toggleWebPrefKey(key: WebPrefKey) {
async toggleWebPrefKey(key: PrefKey) {
const currentValue = (this.state as any)[key];
await this.application.getPrefsService().setUserPrefValue(
await this.application.setPreference(
key,
!currentValue,
true
);
await this.setState({
[key]: !currentValue
})
this.reloadFont();
if (key === WebPrefKey.EditorSpellcheck) {
if (key === PrefKey.EditorSpellcheck) {
/** Allows textarea to reload */
await this.setState({ textareaUnloading: true });
await this.setState({ textareaUnloading: false });
this.reloadFont();
} else if (key === WebPrefKey.EditorResizersEnabled && this.state[key] === true) {
} else if (key === PrefKey.EditorResizersEnabled && this.state[key] === true) {
this.$timeout(() => {
this.leftPanelPuppet!.flash!();
this.rightPanelPuppet!.flash!();

View File

@@ -1,16 +1,5 @@
import { SNNote } from '@standardnotes/snjs';
export enum NoteSortKey {
CreatedAt = 'created_at',
UserUpdatedAt = 'userModifiedDate',
Title = 'title',
/** @legacy Use UserUpdatedAt instead */
UpdatedAt = 'updated_at',
/** @legacy Use UserUpdatedAt instead */
ClientUpdatedAt = 'client_updated_at',
}
export function notePassesFilter(
note: SNNote,
showArchived: boolean,

View File

@@ -6,7 +6,7 @@ import {
removeFromArray,
SNNote,
SNTag,
WebPrefKey,
PrefKey,
findInArray,
CollectionSort,
} from '@standardnotes/snjs';
@@ -17,7 +17,6 @@ import {
PANEL_NAME_NOTES
} from '@/views/constants';
import {
NoteSortKey,
notePassesFilter
} from './note_utils';
import { UuidString } from '@standardnotes/snjs';
@@ -37,13 +36,13 @@ type NotesState = {
noteFilter: { text: string }
mutable: { showMenu: boolean }
completedFullSync: boolean
[WebPrefKey.TagsPanelWidth]?: number
[WebPrefKey.NotesPanelWidth]?: number
[WebPrefKey.EditorWidth]?: number
[WebPrefKey.EditorLeft]?: number
[WebPrefKey.EditorMonospaceEnabled]?: boolean
[WebPrefKey.EditorSpellcheck]?: boolean
[WebPrefKey.EditorResizersEnabled]?: boolean
[PrefKey.TagsPanelWidth]?: number
[PrefKey.NotesPanelWidth]?: number
[PrefKey.EditorWidth]?: number
[PrefKey.EditorLeft]?: number
[PrefKey.EditorMonospaceEnabled]?: boolean
[PrefKey.EditorSpellcheck]?: boolean
[PrefKey.EditorResizersEnabled]?: boolean
}
type NoteFlag = {
@@ -147,8 +146,6 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
this.handleTagChange(this.selectedTag!);
} else if (eventName === AppStateEvent.ActiveEditorChanged) {
this.handleEditorChange();
} else if (eventName === AppStateEvent.PreferencesChanged) {
this.reloadPreferences();
} else if (eventName === AppStateEvent.EditorFocused) {
this.setShowMenuFalse();
}
@@ -166,6 +163,9 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
/** @override */
async onAppEvent(eventName: ApplicationEvent) {
switch (eventName) {
case ApplicationEvent.PreferencesChanged:
this.reloadPreferences();
break;
case ApplicationEvent.SignedIn:
this.appState.closeAllEditors();
this.selectFirstNote();
@@ -420,37 +420,40 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
async reloadPreferences() {
const viewOptions = {} as NotesState;
const prevSortValue = this.getState().sortBy;
let sortBy = this.application!.getPrefsService().getValue(
WebPrefKey.SortNotesBy,
NoteSortKey.CreatedAt
let sortBy = this.application!.getPreference(
PrefKey.SortNotesBy,
CollectionSort.CreatedAt
);
if (sortBy === NoteSortKey.UpdatedAt || sortBy === NoteSortKey.ClientUpdatedAt) {
if (
sortBy === CollectionSort.UpdatedAt ||
(sortBy as string) === "client_updated_at"
) {
/** Use UserUpdatedAt instead */
sortBy = NoteSortKey.UserUpdatedAt;
sortBy = CollectionSort.UpdatedAt;
}
viewOptions.sortBy = sortBy;
viewOptions.sortReverse = this.application!.getPrefsService().getValue(
WebPrefKey.SortNotesReverse,
viewOptions.sortReverse = this.application!.getPreference(
PrefKey.SortNotesReverse,
false
);
viewOptions.showArchived = this.application!.getPrefsService().getValue(
WebPrefKey.NotesShowArchived,
viewOptions.showArchived = this.application!.getPreference(
PrefKey.NotesShowArchived,
false
);
viewOptions.hidePinned = this.application!.getPrefsService().getValue(
WebPrefKey.NotesHidePinned,
viewOptions.hidePinned = this.application!.getPreference(
PrefKey.NotesHidePinned,
false
);
viewOptions.hideNotePreview = this.application!.getPrefsService().getValue(
WebPrefKey.NotesHideNotePreview,
viewOptions.hideNotePreview = this.application!.getPreference(
PrefKey.NotesHideNotePreview,
false
);
viewOptions.hideDate = this.application!.getPrefsService().getValue(
WebPrefKey.NotesHideDate,
viewOptions.hideDate = this.application!.getPreference(
PrefKey.NotesHideDate,
false
);
viewOptions.hideTags = this.application.getPrefsService().getValue(
WebPrefKey.NotesHideTags,
viewOptions.hideTags = this.application.getPreference(
PrefKey.NotesHideTags,
true,
);
const state = this.getState();
@@ -475,8 +478,8 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
}
reloadPanelWidth() {
const width = this.application!.getPrefsService().getValue(
WebPrefKey.NotesPanelWidth
const width = this.application!.getPreference(
PrefKey.NotesPanelWidth
);
if (width && this.panelPuppet!.ready) {
this.panelPuppet!.setWidth!(width);
@@ -495,10 +498,9 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
__: boolean,
isCollapsed: boolean
) {
this.application!.getPrefsService().setUserPrefValue(
WebPrefKey.NotesPanelWidth,
newWidth,
true
this.application!.setPreference(
PrefKey.NotesPanelWidth,
newWidth
);
this.application!.getAppState().panelDidResize(
PANEL_NAME_NOTES,
@@ -541,11 +543,11 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
optionsSubtitle() {
let base = "";
if (this.getState().sortBy === NoteSortKey.CreatedAt) {
if (this.getState().sortBy === CollectionSort.CreatedAt) {
base += " Date Added";
} else if (this.getState().sortBy === NoteSortKey.UserUpdatedAt) {
} else if (this.getState().sortBy === CollectionSort.UpdatedAt) {
base += " Date Modified";
} else if (this.getState().sortBy === NoteSortKey.Title) {
} else if (this.getState().sortBy === CollectionSort.Title) {
base += " Title";
}
if (this.getState().showArchived) {
@@ -709,40 +711,37 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
this.setShowMenuFalse();
}
toggleWebPrefKey(key: WebPrefKey) {
this.application!.getPrefsService().setUserPrefValue(
togglePrefKey(key: PrefKey) {
this.application!.setPreference(
key,
!this.state[key],
true
!this.state[key]
);
}
selectedSortByCreated() {
this.setSortBy(NoteSortKey.CreatedAt);
this.setSortBy(CollectionSort.CreatedAt);
}
selectedSortByUpdated() {
this.setSortBy(NoteSortKey.ClientUpdatedAt);
this.setSortBy(CollectionSort.UpdatedAt);
}
selectedSortByTitle() {
this.setSortBy(NoteSortKey.Title);
this.setSortBy(CollectionSort.Title);
}
toggleReverseSort() {
this.selectedMenuItem();
this.application!.getPrefsService().setUserPrefValue(
WebPrefKey.SortNotesReverse,
!this.getState().sortReverse,
true
this.application!.setPreference(
PrefKey.SortNotesReverse,
!this.getState().sortReverse
);
}
setSortBy(type: NoteSortKey) {
this.application!.getPrefsService().setUserPrefValue(
WebPrefKey.SortNotesBy,
type,
true
setSortBy(type: CollectionSort) {
this.application!.setPreference(
PrefKey.SortNotesBy,
type
);
}

View File

@@ -9,7 +9,7 @@ import {
SNSmartTag,
ComponentArea,
SNComponent,
WebPrefKey,
PrefKey,
UuidString,
TagMutator
} from '@standardnotes/snjs';
@@ -145,10 +145,8 @@ class TagsViewCtrl extends PureViewCtrl<{}, TagState> {
}
/** @override */
onAppStateEvent(eventName: AppStateEvent, data?: any) {
if (eventName === AppStateEvent.PreferencesChanged) {
this.loadPreferences();
} else if (eventName === AppStateEvent.TagChanged) {
onAppStateEvent(eventName: AppStateEvent) {
if (eventName === AppStateEvent.TagChanged) {
this.setState({
selectedTag: this.application.getAppState().getSelectedTag()
});
@@ -159,8 +157,13 @@ class TagsViewCtrl extends PureViewCtrl<{}, TagState> {
/** @override */
async onAppEvent(eventName: ApplicationEvent) {
super.onAppEvent(eventName);
if (eventName === ApplicationEvent.LocalDataIncrementalLoad) {
this.reloadNoteCounts();
switch (eventName) {
case ApplicationEvent.LocalDataIncrementalLoad:
this.reloadNoteCounts();
break;
case ApplicationEvent.PreferencesChanged:
this.loadPreferences();
break;
}
}
@@ -203,7 +206,7 @@ class TagsViewCtrl extends PureViewCtrl<{}, TagState> {
if (!this.panelPuppet.ready) {
return;
}
const width = this.application.getPrefsService().getValue(WebPrefKey.TagsPanelWidth);
const width = this.application.getPreference(PrefKey.TagsPanelWidth);
if (width) {
this.panelPuppet.setWidth!(width);
if (this.panelPuppet.isCollapsed!()) {
@@ -221,11 +224,10 @@ class TagsViewCtrl extends PureViewCtrl<{}, TagState> {
_isAtMaxWidth: boolean,
isCollapsed: boolean
) => {
this.application.getPrefsService().setUserPrefValue(
WebPrefKey.TagsPanelWidth,
newWidth,
true
);
this.application.setPreference(
PrefKey.TagsPanelWidth,
newWidth
).then(() => this.application.sync());
this.application.getAppState().panelDidResize(
PANEL_NAME_TAGS,
isCollapsed

View File

@@ -48,10 +48,16 @@ body {
color: var(--sn-stylekit-info-contrast-color);
}
*:focus {outline:0;}
h1 {
font-size: var(--sn-stylekit-font-size-h1);
}
button:focus {
outline:0;
h2 {
font-size: var(--sn-stylekit-font-size-h2);
}
h3 {
font-size: var(--sn-stylekit-font-size-h3);
}
input, button, select, textarea {

View File

@@ -63,9 +63,16 @@
font-weight: normal;
font-size: var(--sn-stylekit-font-size-h3);
border: none;
border-style: solid;
border-color: transparent;
width: 100%;
position: relative;
&:focus {
outline: 0;
border-color: var(--sn-stylekit-info-color);
border-width: 1px;
}
}
#search-clear-button {

View File

@@ -0,0 +1,38 @@
[data-reach-dialog-overlay] {
z-index: $z-index-modal;
background: none;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: unset;
}
[data-reach-dialog-overlay]::before {
background-color: var(--sn-stylekit-contrast-background-color);
content: "";
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
opacity: 0.75;
}
[data-reach-dialog-content] {
padding: 0;
margin: 0;
position: relative;
overflow: unset;
flex-basis: 0;
}
[data-reach-dialog-content] .sk-modal-content,
[data-reach-dialog-content] .sn-component,
[data-reach-dialog-content] .sk-panel {
height: 100%;
}
[data-reach-alert-dialog-content] {
width: auto;
}

View File

@@ -0,0 +1,48 @@
.sessions-modal {
h2, ul, p {
margin: 0;
padding: 0;
}
h2 {
font-size: var(--sn-stylekit-font-size-h2);
}
ul {
grid-column: 1 / 3;
display: grid;
gap: 16px;
grid-gap: 16px;
}
li {
display: grid;
grid-template-columns: 1fr max-content;
border-bottom: 1px solid var(--sn-stylekit-border-color);
padding-bottom: 16px;
grid-column-gap: 12px;
column-gap: 12px;
}
li:last-of-type {
border: none;
padding-bottom: 0;
}
li > * {
grid-column: 1;
}
li button {
grid-column: 2;
grid-row: 1 / span 3;
align-self: center;
}
.sn-component .sk-panel-content {
padding-bottom: 1.6rem;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
grid-gap: 8px;
gap: 8px;
}
}
.sessions-modal-refreshing {
grid-column: 2;
font-weight: normal;
}

View File

@@ -77,6 +77,12 @@ button.sk-button {
border: none;
}
a {
a, .sk-a {
background: none;
border: none;
color: var(--sn-stylekit-info-color);
}
button.sk-a {
min-height: 24px;
}

View File

@@ -9,3 +9,5 @@
@import "lock-screen";
@import "stylekit-sub";
@import "ionicons";
@import "reach-sub";
@import "sessions-modal";

View File

@@ -163,8 +163,11 @@
.sk-panel-row
a.sk-a.info.sk-panel-row.condensed(
ng-click="self.openPasswordWizard()"
ng-if="!self.state.showBetaWarning"
) Change Password
a.sk-a.info.sk-panel-row.condensed(
ng-click="self.openSessionsModal()"
ng-if="self.state.showSessions"
) Manage Sessions
a.sk-a.info.sk-panel-row.condensed(
ng-click="self.openPrivilegesModal('')",
ng-show='self.state.user'

13118
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,35 +7,34 @@
"url": "https://github.com/standardnotes/web"
},
"scripts": {
"setup": "npm run submodules && npm install",
"setup": "yarn submodules && yarn install",
"start": "webpack-dev-server --progress --config webpack.dev.js",
"watch": "webpack -w --config webpack.dev.js",
"watch:desktop": "webpack -w --config webpack.dev.js --env.platform='desktop'",
"bundle": "webpack --config webpack.prod.js && npm run tsc",
"bundle": "webpack --config webpack.prod.js && yarn tsc",
"bundle:desktop": "webpack --config webpack.prod.js --env.platform='desktop'",
"bundle:desktop:beta": "webpack --config webpack.prod.js --env.platform='desktop' --env.public_beta='true'",
"build": "bundle install && npm ci && bundle exec rails assets:precompile && npm run bundle",
"build": "bundle install && yarn install --pure-lockfile && bundle exec rails assets:precompile && yarn bundle",
"submodules": "git submodule update --init --force",
"lint": "eslint --fix app/assets/javascripts/**/*.js",
"tsc": "tsc --project app/assets/javascripts/tsconfig.json"
},
"devDependencies": {
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.1",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.10.4",
"@types/angular": "^1.7.0",
"@babel/core": "^7.12.10",
"@babel/plugin-transform-react-jsx": "^7.12.10",
"@babel/preset-env": "^7.12.10",
"@babel/preset-typescript": "^7.12.7",
"@types/angular": "^1.8.0",
"@types/chai": "^4.2.11",
"@types/lodash": "^4.14.149",
"@types/mocha": "^7.0.2",
"@types/pug": "^2.0.4",
"@types/react": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^2.23.0",
"@typescript-eslint/parser": "^2.23.0",
"angular": "1.8.0",
"angular": "^1.8.2",
"apply-loader": "^2.0.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-plugin-angularjs-annotate": "^0.10.0",
"chai": "^4.2.0",
"connect": "^3.7.0",
@@ -43,8 +42,6 @@
"dotenv": "^8.2.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0",
"eslint-config-semistandard": "^15.0.0",
"eslint-config-standard": "^14.1.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
@@ -60,8 +57,8 @@
"sass-loader": "^8.0.2",
"serve-static": "^1.14.1",
"sn-stylekit": "2.1.0",
"ts-loader": "^8.0.2",
"typescript": "^3.9.7",
"ts-loader": "^8.0.12",
"typescript": "^4.1.3",
"typescript-eslint": "0.0.1-alpha.0",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
@@ -70,8 +67,13 @@
},
"dependencies": {
"@bugsnag/js": "^7.5.1",
"@reach/alert": "^0.12.1",
"@reach/alert-dialog": "^0.12.1",
"@reach/dialog": "^0.12.1",
"@standardnotes/sncrypto-web": "^1.2.9",
"@standardnotes/snjs": "^2.0.16",
"mobx": "^6.0.1"
"@standardnotes/snjs": "^2.0.30",
"babel-loader": "^8.2.2",
"mobx": "^6.0.4",
"preact": "^10.5.7"
}
}

View File

@@ -3,12 +3,14 @@ const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
require('dotenv').config();
module.exports = (env = {
platform: 'web'
}) => ({
module.exports = (
env = {
platform: 'web',
}
) => ({
entry: './app/assets/javascripts/index.ts',
output: {
filename: './javascripts/app.js'
filename: './javascripts/app.js',
},
plugins: [
new webpack.DefinePlugin({
@@ -19,28 +21,40 @@ module.exports = (env = {
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
filename: './stylesheets/app.css',
ignoreOrder: true // Enable to remove warnings about conflicting order
})
ignoreOrder: true, // Enable to remove warnings about conflicting order
}),
],
resolve: {
extensions: ['.ts', '.js'],
extensions: ['.ts', '.tsx', '.js'],
alias: {
'%': path.resolve(__dirname, 'app/assets/templates'),
'@': path.resolve(__dirname, 'app/assets/javascripts'),
'@Controllers': path.resolve(__dirname, 'app/assets/javascripts/controllers'),
'@Controllers': path.resolve(
__dirname,
'app/assets/javascripts/controllers'
),
'@Views': path.resolve(__dirname, 'app/assets/javascripts/views'),
'@Services': path.resolve(__dirname, 'app/assets/javascripts/services'),
'@node_modules': path.resolve(__dirname, 'node_modules'),
}
react: 'preact/compat',
'react-dom/test-utils': 'preact/test-utils',
'react-dom': 'preact/compat',
},
},
module: {
rules: [
{
test: /\.(js|ts)$/,
exclude: /(node_modules|snjs)/,
use: {
loader: 'babel-loader'
}
test: /\.(js|tsx?)$/,
exclude: /(node_modules)/,
use: [
'babel-loader',
{
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
],
},
{
test: /\.s?css$/,
@@ -49,12 +63,12 @@ module.exports = (env = {
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../', // The base assets directory in relation to the stylesheets
hmr: process.env.NODE_ENV === 'development'
}
hmr: process.env.NODE_ENV === 'development',
},
},
'css-loader',
'sass-loader'
]
'sass-loader',
],
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
@@ -63,36 +77,34 @@ module.exports = (env = {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}
]
outputPath: 'fonts/',
},
},
],
},
{
test: /\.html$/,
exclude: [
path.resolve(__dirname, 'index.html'),
],
exclude: [path.resolve(__dirname, 'index.html')],
use: [
{
loader: 'ng-cache-loader',
options: {
prefix: 'templates:**'
}
}
]
prefix: 'templates:**',
},
},
],
},
{
test: /\.pug$/,
use: [
{
loader: 'apply-loader'
loader: 'apply-loader',
},
{
loader: 'pug-loader'
}
]
}
]
}
loader: 'pug-loader',
},
],
},
],
},
});

8174
yarn.lock Normal file

File diff suppressed because it is too large Load Diff