Merge branch 'release/3.5.12'
This commit is contained in:
9
.babelrc
9
.babelrc
@@ -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"
|
||||
}]
|
||||
]
|
||||
}
|
||||
|
||||
4
.github/workflows/beta.yml
vendored
4
.github/workflows/beta.yml
vendored
@@ -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:
|
||||
|
||||
|
||||
4
.github/workflows/dev.yml
vendored
4
.github/workflows/dev.yml
vendored
@@ -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:
|
||||
|
||||
|
||||
4
.github/workflows/pr.yml
vendored
4
.github/workflows/pr.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/prod.yml
vendored
4
.github/workflows/prod.yml
vendored
@@ -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
4
.gitignore
vendored
@@ -42,3 +42,7 @@ dump.rdb
|
||||
/dist/stylesheets
|
||||
/dist/fonts
|
||||
/dist/@types
|
||||
|
||||
# Yarn
|
||||
yarn-error.log
|
||||
package-lock.json
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -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
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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();
|
||||
|
||||
252
app/assets/javascripts/directives/views/sessionsModal.tsx
Normal file
252
app/assets/javascripts/directives/views/sessionsModal.tsx
Normal 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: '=',
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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/*"],
|
||||
"@/*": ["./*"],
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
|
||||
@@ -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!();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
38
app/assets/stylesheets/_reach-sub.scss
Normal file
38
app/assets/stylesheets/_reach-sub.scss
Normal 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;
|
||||
}
|
||||
48
app/assets/stylesheets/_sessions-modal.scss
Normal file
48
app/assets/stylesheets/_sessions-modal.scss
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -9,3 +9,5 @@
|
||||
@import "lock-screen";
|
||||
@import "stylekit-sub";
|
||||
@import "ionicons";
|
||||
@import "reach-sub";
|
||||
@import "sessions-modal";
|
||||
|
||||
@@ -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
13118
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user