refactor: remove advanced-checklist editor (moved to community plugins) (#1740)

This commit is contained in:
Mo
2022-10-05 06:43:48 -05:00
committed by GitHub
parent ec41d082ff
commit 8ef5a33994
145 changed files with 42 additions and 7918 deletions

View File

@@ -13,11 +13,6 @@ exports.Components = void 0;
var BaseEditorStaticFiles = ['index.html', 'dist', 'package.json'];
var BaseThemeStaticFiles = ['dist', 'package.json'];
var Editors = [
{
identifier: 'org.standardnotes.advanced-checklist',
path: 'Editors/org.standardnotes.advanced-checklist',
static_files: __spreadArray(__spreadArray([], BaseEditorStaticFiles, true), ['build'], false),
},
{
identifier: 'org.standardnotes.code-editor',
path: 'Editors/org.standardnotes.code-editor',

View File

@@ -89,11 +89,6 @@
"base64": "0ae54799c28a336447a63ddabfe43d0c6e18f8c2cd29cfdb1c0174ab96fde248",
"binary": "54a01414a21e46c67fa59c69189a3d34a6548de7539699f2cf0248f9c5059d37"
},
"org.standardnotes.advanced-checklist": {
"version": "0.2.5",
"base64": "077a56ef75958105bc9dc16376bcea2e89aee57b512b43f9e1b33e1d32ea8358",
"binary": "e903a5c592bd03ca28d205d09dcbc7e4329c7cd57fb74b4e0e5fa724c2321a61"
},
"org.standardnotes.file-safe": {
"version": "2.0.16",
"base64": "2bb641344613fc86c340fb29636a68678a13e453d04fb36228af6045e06664db",

View File

@@ -2,11 +2,6 @@ const BaseEditorStaticFiles = ['index.html', 'dist', 'package.json']
const BaseThemeStaticFiles = ['dist', 'package.json']
const Editors = [
{
identifier: 'org.standardnotes.advanced-checklist',
path: 'Editors/org.standardnotes.advanced-checklist',
static_files: [...BaseEditorStaticFiles, 'build'],
},
{
identifier: 'org.standardnotes.code-editor',
path: 'Editors/org.standardnotes.code-editor',

View File

@@ -1,131 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
#### Copied from Create React App ####
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/public/ext.json

View File

@@ -1,115 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.5](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.2.4...@standardnotes/advanced-checklist@0.2.5) (2022-09-13)
**Note:** Version bump only for package @standardnotes/advanced-checklist
## [0.2.4](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.2.3...@standardnotes/advanced-checklist@0.2.4) (2022-08-23)
**Note:** Version bump only for package @standardnotes/advanced-checklist
## [0.2.3](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.2.2...@standardnotes/advanced-checklist@0.2.3) (2022-07-13)
### Bug Fixes
* upgrade jest with types to latest version ([09e08ca](https://github.com/standardnotes/app/commit/09e08ca899ba8694cf43292e918c4c204c0d2cb9))
* upgrade ts-jest in packages ([71e792d](https://github.com/standardnotes/app/commit/71e792da354ff90335b92758e196075a0f88d060))
## [0.2.2](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.2.1...@standardnotes/advanced-checklist@0.2.2) (2022-07-06)
### Bug Fixes
* **advanced checklist:** improve editor styles ([#1221](https://github.com/standardnotes/app/issues/1221)) ([f7ba658](https://github.com/standardnotes/app/commit/f7ba6588a7d062e3ec82e6413042ce5d8cd075f7))
## [0.2.1](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.2.0...@standardnotes/advanced-checklist@0.2.1) (2022-07-06)
**Note:** Version bump only for package @standardnotes/advanced-checklist
# [0.2.0](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.1.6...@standardnotes/advanced-checklist@0.2.0) (2022-07-06)
### Features
* add utils package ([aef4ceb](https://github.com/standardnotes/app/commit/aef4ceb7f85948f1f08b8b09a4db5d187daa371b))
## [0.1.6](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.1.5...@standardnotes/advanced-checklist@0.1.6) (2022-07-05)
### Bug Fixes
* **advanced checklist:** remove test for reorder icon ([#1209](https://github.com/standardnotes/app/issues/1209)) ([296aa9a](https://github.com/standardnotes/app/commit/296aa9aab2072d4aad68485c31c000ad2c3bf013))
## [0.1.5](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.1.4...@standardnotes/advanced-checklist@0.1.5) (2022-07-05)
### Bug Fixes
* **advanced checklist:** UI changes ([#1208](https://github.com/standardnotes/app/issues/1208)) ([1ce4cb3](https://github.com/standardnotes/app/commit/1ce4cb3c5c8f4e590fd67fdbd684b36ac6383bd9))
## [0.1.4](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.1.3...@standardnotes/advanced-checklist@0.1.4) (2022-07-04)
### Bug Fixes
* **advanced checklist:** animations and error handling ([#1200](https://github.com/standardnotes/app/issues/1200)) ([a0205a5](https://github.com/standardnotes/app/commit/a0205a5c7dd72184cb575195ced3132091072239))
## [0.1.3](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.1.2...@standardnotes/advanced-checklist@0.1.3) (2022-06-30)
### Bug Fixes
* debounce saving task description/draft ([#1187](https://github.com/standardnotes/app/issues/1187)) ([47a0551](https://github.com/standardnotes/app/commit/47a0551967ca420a957e2123d56bd7f0c8a95c53))
## [0.1.2](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.1.1...@standardnotes/advanced-checklist@0.1.2) (2022-06-29)
### Bug Fixes
* **advanced checklist:** style and layout improvements ([#1182](https://github.com/standardnotes/app/issues/1182)) ([6c19adb](https://github.com/standardnotes/app/commit/6c19adba1902ef054f501d57f6e284fbf44ca28b))
## [0.1.1](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.1.0...@standardnotes/advanced-checklist@0.1.1) (2022-06-28)
**Note:** Version bump only for package @standardnotes/advanced-checklist
# [0.1.0](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.0.5...@standardnotes/advanced-checklist@0.1.0) (2022-06-28)
### Features
* **advanced checklist:** collapsible group sections ([#1167](https://github.com/standardnotes/app/issues/1167)) ([59e5324](https://github.com/standardnotes/app/commit/59e5324a29029d024811bf2bb63e08ae42d3b62b))
* deprecated editors ([#1166](https://github.com/standardnotes/app/issues/1166)) ([60ca415](https://github.com/standardnotes/app/commit/60ca4150446f9a14bb6a31416686c6d07a7d0cd9))
* **web:** tailwind css ([#1147](https://github.com/standardnotes/app/issues/1147)) ([b80038f](https://github.com/standardnotes/app/commit/b80038f607d7411912fa99366abf559a44874ef3))
## [0.0.5](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.0.5-alpha.0...@standardnotes/advanced-checklist@0.0.5) (2022-06-22)
**Note:** Version bump only for package @standardnotes/advanced-checklist
## [0.0.5-alpha.0](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.0.4...@standardnotes/advanced-checklist@0.0.5-alpha.0) (2022-06-22)
### Bug Fixes
* components scripts ([#1136](https://github.com/standardnotes/app/issues/1136)) ([e80b4d0](https://github.com/standardnotes/app/commit/e80b4d0ffad495c758b593c30e1c4c754dda9b7e))
## 0.0.4 (2022-06-16)
**Note:** Version bump only for package @standardnotes/advanced-checklist
## 0.0.3 (2022-06-16)
**Note:** Version bump only for package @standardnotes/advanced-checklist
## [0.0.2](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.0.2-alpha.3...@standardnotes/advanced-checklist@0.0.2) (2022-06-16)
**Note:** Version bump only for package @standardnotes/advanced-checklist
## [0.0.2-alpha.3](https://github.com/standardnotes/app/compare/@standardnotes/advanced-checklist@0.0.2-alpha.2...@standardnotes/advanced-checklist@0.0.2-alpha.3) (2022-06-16)
**Note:** Version bump only for package @standardnotes/advanced-checklist
## 0.0.2-alpha.2 (2022-06-16)
**Note:** Version bump only for package @standardnotes/advanced-checklist
## 0.0.2-alpha.1 (2022-06-16)
**Note:** Version bump only for package @standardnotes/advanced-checklist
## 0.0.2-alpha.0 (2022-06-15)
**Note:** Version bump only for package @standardnotes/advanced-checklist

View File

@@ -1,80 +0,0 @@
# advanced-checklist
A great way to manage short-term and long-term to-do's. You can mark tasks as completed, change their order, and edit the text naturally in place.
## Development
**Prerequisites:** Install [Node.js](https://nodejs.org/en/), [Yarn](https://classic.yarnpkg.com/en/docs/install/), and [Git](https://github.com/git-guides/install-git) on your computer.
The general instructions setting up an environment to develop Standard Notes extensions can be found [here](https://docs.standardnotes.org/extensions/local-setup). You can also follow these instructions:
1. Fork the [repository](https://github.com/standardnotes/advanced-checklist) on GitHub.
1. [Clone](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) your fork of the repository.
1. Run `cd advanced-checklist` to enter the `advanced-checklist` directory.
1. Run `yarn install` to install the dependencies on your machine as they are described in `yarn.lock`.
### Testing in the browser
1. To run the app in development mode, run `yarn start` and visit http://localhost:8001. Press `ctrl/cmd + C` to exit development mode.
### Testing in the Standard Notes app
1. Create an `ext.json` in the `public` directory. You have three options:
1. Use `sample.ext.json`.
1. Create `ext.json` as a copy of `sample.ext.json`.
1. Follow the instructions [here](https://docs.standardnotes.org/extensions/local-setup) with `url: "http://localhost:3000/index.html"`.
1. Install http-server using `sudo npm install -g http-server` then run `yarn server` to serve the `./build` directory at http://localhost:3000.
1. To build the app, run `yarn build`.
1. Install the editor into the [web](https://app.standardnotes.org) or [desktop](https://standardnotes.org/download) app with `http://localhost:3000/sample.ext.json` or with your custom `ext.json`. Press `ctrl/cmd + C` to shut down the server.
### Deployment
1. To make the source code prettier, run `yarn pretty`.
1. To the deploy the build into the `gh-pages` branch of your repository on GitHub, run `yarn deploy-stable`.
1. To deploy the build into to the `dev` branch for testing, run `yarn deploy-dev`.
1. To deploy the built into the `build` branch for distributing, run `yarn deploy-build` for distributing builds.
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
### Available Scripts
In the project directory, you can run:
#### `yarn start`
Runs the app in the development mode.\
Open [http://localhost:8001](http://localhost:8001) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
#### `yarn test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
#### `yarn build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
#### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
### Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

View File

@@ -1,2 +0,0 @@
const override = require('../cra-app.override')
module.exports = override

View File

@@ -1,127 +0,0 @@
{
"name": "@standardnotes/advanced-checklist",
"version": "0.2.5",
"description": "A task editor with grouping functionality.",
"author": "Standard Notes.",
"keywords": [
"Standard Notes",
"Standard Notes Extensions"
],
"private": true,
"license": "AGPL-3.0-or-later",
"sn": {
"main": "build/index.html"
},
"homepage": ".",
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "react-app-rewired start",
"test:coverage": "npm run test -- --coverage --watchAll --no-silent",
"eject": "react-scripts eject",
"components:compile": "react-app-rewired build",
"test": "react-app-rewired test --watchAll=false --silent",
"format": "prettier --write 'src/**/*.{html,css,scss,js,jsx,ts,tsx,json}' README.md"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"lint-staged": {
"README.md": [
"prettier --write"
],
"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
"prettier --write"
]
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}",
"!<rootDir>/node_modules/"
],
"coveragePathIgnorePatterns": [
"<rootDir>/src/mockData.ts",
"<rootDir>/src/app/hooks.ts",
"<rootDir>/src/app/store.ts",
"<rootDir>/src/app/listenerMiddleware.ts"
],
"coverageReporters": [
"text",
"html"
],
"coverageThreshold": {
"global": {
"branches": 90,
"functions": 100,
"lines": 100,
"statements": 100
}
},
"transform": {
"^.+\\.(ts|tsx)$": "ts-jest"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
"^.+\\.module\\.(css|sass|scss)$"
]
},
"dependencies": {
"@standardnotes/utils": "workspace:*"
},
"devDependencies": {
"@reach/alert-dialog": "0.16.2",
"@reach/menu-button": "0.16.2",
"@reach/visually-hidden": "0.16.0",
"@react-hook/resize-observer": "^1.2.5",
"@reduxjs/toolkit": "1.8.0",
"@standardnotes/editor-kit": "2.2.5",
"@standardnotes/stylekit": "5.23.0",
"@testing-library/dom": "8.11.3",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.4",
"@testing-library/user-event": "13.5.0",
"@types/jest": "^28.1.5",
"@types/lodash": "4.14.179",
"@types/node": "17.0.21",
"@types/react": "17.0.40",
"@types/react-beautiful-dnd": "13.1.2",
"@types/react-dom": "17.0.13",
"@types/react-redux": "7.1.23",
"@types/react-transition-group": "4.4.4",
"@types/redux-mock-store": "1.0.3",
"@types/styled-components": "5.1.24",
"@types/uuid": "8.3.4",
"gh-pages": "3.2.3",
"lint-staged": "12.3.5",
"node-sass": "*",
"prettier": "*",
"react": "17.0.2",
"react-app-rewired": "^2.2.1",
"react-beautiful-dnd": "13.1.0",
"react-dom": "17.0.2",
"react-redux": "7.2.8",
"react-scripts": "5.0.0",
"react-transition-group": "4.4.2",
"redux": "4.1.2",
"redux-mock-store": "1.5.4",
"source-map-explorer": "2.5.2",
"styled-components": "5.3.5",
"ts-jest": "^28.0.5",
"typescript": "4.6.2",
"uuid": "8.3.2"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,44 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="A great way to manage short-term and long-term to-do's. You can mark tasks as completed, change their order, and edit the text naturally in place."
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Advanced Checklist</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div class="sn-component" id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -1,25 +0,0 @@
{
"short_name": "Advanced Checklist",
"name": "Advanced Checklist",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -1,12 +0,0 @@
{
"identifier": "org.standardnotes.advanced-checklist-dev",
"name": "Advanced Checklist - Dev",
"content_type": "SN|Component",
"area": "editor-editor",
"version": "0.1.0",
"description": "A task editor with grouping functionality.",
"url": "http://localhost:3000/index.html",
"download_url": "",
"latest_url": "",
"thumbnail_url": ""
}

View File

@@ -1,54 +0,0 @@
import useResizeObserver from '@react-hook/resize-observer'
import React, { useEffect, useRef, useState } from 'react'
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useDidMount = (effect: React.EffectCallback, deps?: React.DependencyList) => {
const [didMount, setDidMount] = useState(false)
useEffect(() => {
if (didMount) {
effect()
} else {
setDidMount(true)
}
}, [deps, didMount, effect])
}
export const useResize = (ref: React.RefObject<HTMLElement>, effect: (target: HTMLElement) => void) => {
const [size, setSize] = useState<DOMRect>()
function isDeepEqual(prevSize?: DOMRect, nextSize?: DOMRect) {
return JSON.stringify(prevSize) === JSON.stringify(nextSize)
}
useResizeObserver(ref, ({ contentRect, target }) => {
if (!isDeepEqual(size, contentRect)) {
setSize(contentRect)
effect(target as HTMLElement)
}
})
}
export const useDebouncedCallback = (callback: () => void, waitMs: number = 500) => {
const timeout = useRef<any>()
clearTimeout(timeout.current)
timeout.current = setTimeout(() => {
callback()
}, waitMs)
}
export const usePrevious = (value: any) => {
const ref = useRef<typeof value>()
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}

View File

@@ -1,46 +0,0 @@
import { createListenerMiddleware, isAnyOf } from '@reduxjs/toolkit'
import {
deleteAllCompleted,
openAllCompleted,
taskAdded,
taskDeleted,
taskModified,
tasksGroupAdded,
tasksGroupCollapsed,
tasksGroupDeleted,
tasksGroupLastActive,
tasksGroupMerged,
tasksReordered,
taskToggled,
} from '../features/tasks/tasks-slice'
const listenerMiddleware = createListenerMiddleware()
/**
* A list of actions that we want to listen to.
* The groupName is obtained from the payload, and we use it to
* dispatch the tasksGroupLastActive action.
*/
const actionsWithGroup = isAnyOf(
taskAdded,
taskModified,
taskToggled,
taskDeleted,
openAllCompleted,
deleteAllCompleted,
tasksReordered,
tasksGroupAdded,
tasksGroupDeleted,
tasksGroupMerged,
tasksGroupCollapsed,
)
listenerMiddleware.startListening({
matcher: actionsWithGroup,
effect: ({ payload }, listenerApi) => {
const { groupName } = payload
listenerApi.dispatch(tasksGroupLastActive({ groupName }))
},
})
export default listenerMiddleware.middleware

View File

@@ -1,16 +0,0 @@
import { configureStore } from '@reduxjs/toolkit'
import settingsReducer from '../features/settings/settings-slice'
import tasksReducer from '../features/tasks/tasks-slice'
import listenerMiddleware from './listenerMiddleware'
export const store = configureStore({
reducer: {
tasks: tasksReducer,
settings: settingsReducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(listenerMiddleware),
})
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>

View File

@@ -1,38 +0,0 @@
import { ChangeEvent, forwardRef, MouseEvent } from 'react'
type CheckBoxInputProps = {
checked?: boolean
disabled?: boolean
testId?: string
onChange?: (event: ChangeEvent<HTMLInputElement>) => void
onClick?: (event: MouseEvent<SVGElement>) => void
}
export const CheckBoxInput = forwardRef<HTMLInputElement, CheckBoxInputProps>(
({ checked, disabled, testId, onChange, onClick }, ref) => {
return (
<label className="checkbox-container">
<input
className="checkbox-state"
type="checkbox"
checked={checked}
data-testid={testId}
disabled={disabled}
onChange={onChange}
ref={ref}
/>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="3 2 22 20"
className="checkbox-button"
onClick={onClick}
>
<use xlinkHref="#checkbox-square" className="checkbox-square"></use>
<use xlinkHref="#checkbox-mark" className="checkbox-mark"></use>
<use xlinkHref="#checkbox-circle" className="checkbox-circle"></use>
</svg>
</label>
)
},
)

View File

@@ -1,48 +0,0 @@
import React, { useEffect, useState } from 'react'
import styled from 'styled-components'
const ProgressBarBackground = styled.circle`
fill: none;
stroke: var(--sn-stylekit-neutral-color);
`
const ProgressBarStroke = styled.circle`
fill: none;
stroke: var(--sn-stylekit-info-color);
transition: all 0.5s;
`
type CircularProgressBarProps = {
size: number
percentage: number
}
export const CircularProgressBar: React.FC<CircularProgressBarProps> = ({ size, percentage }) => {
const [progress, setProgress] = useState(0)
useEffect(() => {
setProgress(percentage)
}, [percentage])
const viewBox = `0 0 ${size} ${size}`
const strokeWidth = size * (0.1 / 100) * 100 + 1
const radius = (size - strokeWidth) / 2
const circumference = radius * Math.PI * 2
const dash = (progress * circumference) / 100
return (
<svg height={size} viewBox={viewBox} width={size} data-testid="circular-progress-bar">
<ProgressBarBackground cx={size / 2} cy={size / 2} r={radius} strokeWidth={strokeWidth} />
<ProgressBarStroke
cx={size / 2}
cy={size / 2}
r={radius}
strokeWidth={strokeWidth}
transform={`rotate(-90 ${size / 2} ${size / 2})`}
strokeDasharray={`${dash} ${circumference - dash}`}
strokeLinecap="round"
style={{ transition: 'all 0.5s' }}
/>
</svg>
)
}

View File

@@ -1,72 +0,0 @@
import '@reach/dialog/styles.css'
import { AlertDialog, AlertDialogDescription, AlertDialogLabel } from '@reach/alert-dialog'
import React, { useRef } from 'react'
import { sanitizeHtmlString } from '@standardnotes/utils'
type ConfirmDialogProps = {
testId?: string
title?: string
confirmButtonText?: string
confirmButtonStyle?: 'danger' | 'info'
confirmButtonCb: () => void
cancelButtonText?: string
cancelButtonCb: () => void
}
export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
testId,
title = '',
confirmButtonText = 'Confirm',
confirmButtonStyle = 'info',
confirmButtonCb,
cancelButtonText = 'Cancel',
cancelButtonCb,
children,
}) => {
const cancelRef = useRef<HTMLButtonElement>(null)
return (
<AlertDialog data-testid={testId} onDismiss={cancelButtonCb} leastDestructiveRef={cancelRef}>
<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"
dangerouslySetInnerHTML={{
__html: sanitizeHtmlString(title),
}}
/>
<AlertDialogDescription className="sk-panel-row">
<p className="color-foreground">{children}</p>
</AlertDialogDescription>
<div className="flex my-1">
<button
data-testid="cancel-dialog-button"
className="sn-button small neutral"
onClick={cancelButtonCb}
ref={cancelRef}
>
{cancelButtonText}
</button>
<button
data-testid="confirm-dialog-button"
className={`sn-button small ml-2 ${confirmButtonStyle}`}
onClick={confirmButtonCb}
>
{confirmButtonText}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</AlertDialog>
)
}

View File

@@ -1,7 +0,0 @@
import styled from 'styled-components'
export const GenericInlineText = styled.span`
color: var(--sn-stylekit-paragraph-text-color);
font-size: var(--sn-stylekit-font-size-p);
margin: 0 10px;
`

View File

@@ -1,26 +0,0 @@
import styled from 'styled-components'
type Header1Props = {
crossed: boolean
}
const Header1 = styled.h1<Header1Props>`
color: var(--sn-stylekit-editor-foreground-color);
display: inline;
font-size: 1.125rem !important;
margin-right: 10px !important;
text-decoration: ${({ crossed }) => (crossed ? 'line-through' : 'none')};
`
type MainTitleProps = {
highlight?: boolean
crossed?: boolean
}
export const MainTitle: React.FC<MainTitleProps> = ({ children, highlight = false, crossed = false, ...props }) => {
return (
<Header1 className={`sk-h1 ${highlight ? 'info' : ''}`} crossed={crossed} {...props}>
{children}
</Header1>
)
}

View File

@@ -1,13 +0,0 @@
type RoundButtonProps = {
testId?: string
onClick: () => void
size?: 'normal' | 'small'
}
export const RoundButton: React.FC<RoundButtonProps> = ({ testId, onClick, children, size = 'normal' }) => {
return (
<button data-testid={testId} className={`sn-icon-button ${size}`} onClick={onClick}>
{children}
</button>
)
}

View File

@@ -1,14 +0,0 @@
import styled from 'styled-components'
export const SubTitle = styled.h3`
color: var(--sn-stylekit-foreground-color);
cursor: pointer;
font-size: var(--sn-stylekit-font-size-h3);
font-weight: 500;
margin: 10px 0px;
opacity: 0.55;
&::first-letter {
text-transform: capitalize;
}
`

View File

@@ -1,46 +0,0 @@
import { ChangeEvent, forwardRef, KeyboardEvent } from 'react'
import styled from 'styled-components'
const StyledTextArea = styled.textarea`
background-color: transparent;
border: none;
color: inherit;
font-size: 0.98rem;
font-weight: 400;
margin-left: 2px;
outline: none;
overflow: hidden;
resize: none;
width: 100%;
`
type TextAreaInputProps = {
value: string
className?: string
dir?: 'ltr' | 'rtl' | 'auto'
disabled?: boolean
spellCheck?: boolean
testId?: string
onChange?: (event: ChangeEvent<HTMLTextAreaElement>) => void
onKeyPress?: (event: KeyboardEvent<HTMLTextAreaElement>) => void
onKeyUp?: (event: KeyboardEvent<HTMLTextAreaElement>) => void
}
export const TextAreaInput = forwardRef<HTMLTextAreaElement, TextAreaInputProps>(
({ value, className, dir = 'auto', disabled, spellCheck, testId, onChange, onKeyPress, onKeyUp }, ref) => {
return (
<StyledTextArea
className={className}
data-testid={testId}
dir={dir}
disabled={disabled}
onChange={onChange}
onKeyPress={onKeyPress}
onKeyUp={onKeyUp}
ref={ref}
spellCheck={spellCheck}
value={value}
/>
)
},
)

View File

@@ -1,21 +0,0 @@
$transition-duration: 750ms;
@keyframes strike {
from {
text-decoration: line-through 1px solid transparent;
}
to {
text-decoration: line-through 1px solid var(--sn-stylekit-info-color);
}
}
.cross-out {
animation-duration: $transition-duration;
animation-fill-mode: forwards;
animation-name: strike;
animation-timing-function: linear;
}
.no-text-decoration {
text-decoration: none !important;
}

View File

@@ -1,84 +0,0 @@
import './TextInput.scss'
import { ChangeEvent, FocusEvent, forwardRef, KeyboardEvent } from 'react'
import styled from 'styled-components'
type StyledInputProps = {
textSize: 'normal' | 'big'
}
const StyledInput = styled.input<StyledInputProps>`
background-color: unset;
border: none;
color: var(--sn-stylekit-foreground-color);
font-size: ${({ textSize }) => (textSize === 'big' ? '1.125rem' : 'var(--sn-stylekit-font-size-h3)')};
font-weight: ${({ textSize }) => (textSize === 'big' ? '500' : '400')};
height: auto;
margin: 6px 0 6px 0;
outline: none;
padding: 0;
width: 100%;
/* Remove default shadow for iOS mobile */
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
::placeholder {
color: var(--sn-stylekit-input-placeholder-color);
}
`
type TextInputProps = {
value: string
autoFocus?: boolean
dir?: 'ltr' | 'rtl' | 'auto'
disabled?: boolean
enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
placeholder?: string
spellCheck?: boolean
testId?: string
textSize?: 'normal' | 'big'
onBlur?: (event: FocusEvent<HTMLInputElement>) => void
onChange?: (event: ChangeEvent<HTMLInputElement>) => void
onKeyPress?: (event: KeyboardEvent<HTMLInputElement>) => void
}
export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
(
{
value,
autoFocus,
dir = 'auto',
disabled,
enterKeyHint,
placeholder,
spellCheck,
testId,
textSize = 'normal',
onBlur,
onChange,
onKeyPress,
},
ref,
) => {
return (
<StyledInput
type="text"
autoFocus={autoFocus}
data-testid={testId}
dir={dir}
disabled={disabled}
enterKeyHint={enterKeyHint}
onBlur={onBlur}
onChange={onChange}
onKeyPress={onKeyPress}
placeholder={placeholder}
ref={ref}
spellCheck={spellCheck}
textSize={textSize}
value={value}
/>
)
},
)

View File

@@ -1,15 +0,0 @@
import styled from 'styled-components'
export const WideButton = styled.button`
align-items: center;
background-color: var(--sn-stylekit-background-color);
border-radius: 8px;
border: 1px solid var(--sn-stylekit-border-color);
box-sizing: border-box;
color: var(--sn-stylekit-paragraph-text-color);
cursor: pointer;
display: flex;
height: 36px;
justify-content: center;
width: 100%;
`

View File

@@ -1,14 +0,0 @@
export const AddIcon = () => {
return (
<svg
className="sn-icon small block"
fill="none"
height="14"
viewBox="0 0 14 14"
width="14"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M14 7C14 7.55228 13.5523 8 13 8H8V13C8 13.5523 7.55228 14 7 14C6.44772 14 6 13.5523 6 13V8H1C0.447715 8 0 7.55228 0 7C0 6.44772 0.447715 6 1 6H6V1C6 0.447715 6.44772 0 7 0C7.55228 0 8 0.447715 8 1V6H13C13.5523 6 14 6.44772 14 7Z" />
</svg>
)
}

View File

@@ -1,7 +0,0 @@
export const ChevronDownIcon = () => {
return (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="sn-icon block">
<path d="M6.17622 7.15015L10.0012 10.9751L13.8262 7.15015L15.0012 8.33348L10.0012 13.3335L5.00122 8.33348L6.17622 7.15015Z" />
</svg>
)
}

View File

@@ -1,7 +0,0 @@
export const ChevronUpIcon = () => {
return (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="sn-icon block">
<path d="M13.826 13.3335L10.001 9.5085L6.17597 13.3335L5.00097 12.1502L10.001 7.15017L15.001 12.1502L13.826 13.3335Z" />
</svg>
)
}

View File

@@ -1,12 +0,0 @@
export const DottedCircleIcon = () => {
return (
<svg
className="sn-icon no-fill stroke-neutral-color block"
fill="none"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="0.5" y="0.5" width="19" height="19" rx="9.5" strokeDasharray="2 2" />
</svg>
)
}

View File

@@ -1,7 +0,0 @@
export const MergeIcon = () => {
return (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="sn-icon block">
<path d="M8 17L12 13H15.2C15.6 14.2 16.7 15 18 15C19.7 15 21 13.7 21 12C21 10.3 19.7 9 18 9C16.7 9 15.6 9.8 15.2 11H12L8 7V3H3V8H6L10.2 12L6 16H3V21H8V17Z" />
</svg>
)
}

View File

@@ -1,17 +0,0 @@
import styled from 'styled-components'
const Ellipsis = ({ ...props }) => (
<span aria-hidden {...props}>
</span>
)
/**
* SVG icons don't work well with the MenuButton components.
* So we create a text-based replacement for it.
*/
export const MoreIcon = styled(Ellipsis)`
font-weight: 800;
height: 18px;
line-height: 10px;
`

View File

@@ -1,7 +0,0 @@
export const RenameIcon = () => {
return (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="sn-icon block">
<path d="M11.7167 7.5L12.5 8.28333L4.93333 15.8333H4.16667V15.0667L11.7167 7.5ZM14.7167 2.5C14.5083 2.5 14.2917 2.58333 14.1333 2.74167L12.6083 4.26667L15.7333 7.39167L17.2583 5.86667C17.5833 5.54167 17.5833 5 17.2583 4.69167L15.3083 2.74167C15.1417 2.575 14.9333 2.5 14.7167 2.5ZM11.7167 5.15833L2.5 14.375V17.5H5.625L14.8417 8.28333L11.7167 5.15833Z" />
</svg>
)
}

View File

@@ -1,17 +0,0 @@
type ReorderIconProps = {
highlight?: boolean
}
export const ReorderIcon: React.FC<ReorderIconProps> = ({ highlight = false }) => {
return (
<svg
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`sn-icon block ${highlight ? 'info' : 'neutral'}`}
data-testid="reorder-icon"
>
<path d="M17 5V6.66667H3V5H17ZM3 15H17V13.3333H3V15ZM3 10.8333H17V9.16667H3V10.8333Z" />
</svg>
)
}

View File

@@ -1,7 +0,0 @@
export const TrashIcon = () => {
return (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="sn-icon block">
<path d="M7.49992 2.5V3.33333H3.33325V5H4.16659V15.8333C4.16659 16.2754 4.34218 16.6993 4.65474 17.0118C4.9673 17.3244 5.39122 17.5 5.83325 17.5H14.1666C14.6086 17.5 15.0325 17.3244 15.3451 17.0118C15.6577 16.6993 15.8333 16.2754 15.8333 15.8333V5H16.6666V3.33333H12.4999V2.5H7.49992ZM5.83325 5H14.1666V15.8333H5.83325V5ZM7.49992 6.66667V14.1667H9.16658V6.66667H7.49992ZM10.8333 6.66667V14.1667H12.4999V6.66667H10.8333Z" />
</svg>
)
}

View File

@@ -1,9 +0,0 @@
export { AddIcon } from './AddIcon'
export { ChevronDownIcon } from './ChevronDownIcon'
export { ChevronUpIcon } from './ChevronUpIcon'
export { DottedCircleIcon } from './DottedCircleIcon'
export { MergeIcon } from './MergeIcon'
export { MoreIcon } from './MoreIcon'
export { RenameIcon } from './RenameIcon'
export { ReorderIcon } from './ReorderIcon'
export { TrashIcon } from './TrashIcon'

View File

@@ -1,10 +0,0 @@
export * from './CheckBoxInput'
export * from './CircularProgressBar'
export * from './ConfirmDialog'
export * from './GenericInlineText'
export * from './MainTitle'
export * from './RoundButton'
export * from './SubTitle'
export * from './TextAreaInput'
export * from './TextInput'
export * from './WideButton'

View File

@@ -1,114 +0,0 @@
$transition-duration: 750ms;
.checkbox-container {
display: block;
padding-left: 22px;
position: relative;
&:last-child {
border-bottom: none;
}
}
.checkbox-state {
height: 0;
left: 0;
opacity: 0;
position: absolute;
top: 0;
width: 0;
}
.checkbox-button {
bottom: 0;
cursor: pointer;
fill: var(--sn-stylekit-contrast-background-color);
height: 18px;
left: 0;
margin: auto;
position: absolute;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 1;
top: 0;
width: 18px;
}
.align-baseline {
.checkbox-button {
top: -10px !important;
}
}
.checkbox-square,
.checkbox-mark {
cursor: pointer;
transition: stroke-dashoffset $transition-duration cubic-bezier(0.9, 0, 0.5, 1);
}
.checkbox-circle {
animation-delay: 1s;
animation: none $transition-duration linear;
stroke-dasharray: 1 6;
stroke-width: 0;
stroke: var(--sn-stylekit-neutral-color);
transform-origin: 13.5px 12.5px;
transform: scale(0.4) rotate(0deg);
}
.checkbox-square {
stroke-dasharray: 56.1053, 56.1053;
stroke-dashoffset: 0;
stroke: var(--sn-stylekit-shadow-color);
transition-delay: $transition-duration * 0.2;
fill: var(--sn-stylekit-contrast-background-color);
}
.checkbox-mark {
stroke-dasharray: 9.8995, 9.8995;
stroke-dashoffset: 9.8995;
stroke: var(--sn-stylekit-neutral-color);
transition-duration: $transition-duration * 0.4;
}
.checkbox-circle {
animation-delay: $transition-duration * 0.7;
animation-duration: $transition-duration * 0.7;
}
.checkbox-state:checked {
~ .checkbox-button .checkbox-square {
stroke-dashoffset: 56.1053;
stroke: var(--sn-stylekit-info-color);
transition-delay: 0s;
}
~ .checkbox-button .checkbox-mark {
stroke-dashoffset: 0;
stroke: var(--sn-stylekit-info-color);
transition-delay: $transition-duration * 0.6;
}
}
@keyframes explode {
30% {
stroke: var(--sn-stylekit-info-color);
stroke-opacity: 1;
stroke-width: 3;
transform: scale(0.8) rotate(40deg);
}
100% {
stroke: var(--sn-stylekit-neutral-color);
stroke-opacity: 0;
stroke-width: 0;
transform: scale(1.1) rotate(60deg);
}
}
.explode {
.checkbox-circle {
animation: explode ease $transition-duration;
animation-delay: $transition-duration;
animation-fill-mode: forwards;
}
}

View File

@@ -1,16 +0,0 @@
import './CheckBoxElementsDefs.scss'
export const CheckBoxElementsDefs = () => {
return (
<svg viewBox="0 0 0 0" style={{ position: 'absolute', zIndex: -1, opacity: 0 }}>
<defs>
<path
id="checkbox-square"
d="M21 12.7v5c0 1.3-1 2.3-2.3 2.3H8.3C7 20 6 19 6 17.7V7.3C6 6 7 5 8.3 5h10.4C20 5 21 6 21 7.3v5.4"
></path>
<path id="checkbox-mark" d="M10 13l2 2 5-5"></path>
<circle id="checkbox-circle" cx="13.5" cy="12.5" r="10"></circle>
</defs>
</svg>
)
}

View File

@@ -1,296 +0,0 @@
import { DEFAULT_SECTIONS, GroupModel, TaskModel } from '../features/tasks/tasks-slice'
import {
arrayDefault,
arrayMoveImmutable,
arrayMoveMutable,
getPercentage,
getPlainPreview,
getTaskArrayFromGroupedTasks,
groupTasksByCompletedStatus,
parseMarkdownTasks,
truncateText,
} from './utils'
describe('arrayMoveMutable', () => {
it('should not mutate array if there are no elements', () => {
const theArray: any[] = []
arrayMoveMutable(theArray, 0, 1)
expect(theArray).toHaveLength(0)
})
test('passing a negative number to fromIndex should use 0 instead', () => {
const theArray = ['test', 'another test']
arrayMoveMutable(theArray, -1, 1)
expect(theArray).toHaveLength(2)
expect(theArray[0]).toBe('test')
expect(theArray[1]).toBe('another test')
})
test('passing a negative number to toIndex should use 0 instead', () => {
const theArray = ['test', 'another test']
arrayMoveMutable(theArray, 1, -1)
expect(theArray).toHaveLength(2)
expect(theArray[0]).toBe('test')
expect(theArray[1]).toBe('another test')
})
})
describe('arrayMoveImmutable', () => {
it('should move the element to the desired position', () => {
const theArray = ['test', 'testing']
const newArray = arrayMoveImmutable(theArray, 0, 1)
expect(theArray).toHaveLength(2)
expect(theArray[0]).toBe('test')
expect(theArray[1]).toBe('testing')
expect(newArray).toHaveLength(2)
expect(newArray[0]).toBe('testing')
expect(newArray[1]).toBe('test')
})
})
describe('getPercentage', () => {
it('should return 0 if the first number is 0', () => {
const percentage = getPercentage(0, 1)
expect(percentage).toBe(0)
})
it('should return 0 if the second number is 0', () => {
const percentage = getPercentage(1, 0)
expect(percentage).toBe(0)
})
it('should swap first number with second number, if the later is greater', () => {
const percentage = getPercentage(10, 1)
expect(percentage).toBe(10)
})
it('should trucate numbers up to two places', () => {
expect(getPercentage(38.2, 125)).toBe(30.56)
expect(getPercentage(67.55, 125)).toBe(54.04)
expect(getPercentage(86.65, 125)).toBe(69.32)
expect(getPercentage(98.85, 125)).toBe(79.08)
})
it('should return the percentage of two numbers', () => {
expect(getPercentage(4, 20)).toBe(20)
expect(getPercentage(10, 10)).toBe(100)
expect(getPercentage(10, 100)).toBe(10)
expect(getPercentage(10, 40)).toBe(25)
expect(getPercentage(15, 30)).toBe(50)
})
})
describe('groupTasksByCompletedStatus', () => {
it('should return open tasks and completed tasks', () => {
const tasks: TaskModel[] = [
{
id: 'test-1',
description: 'Testing #1',
completed: false,
createdAt: new Date(),
},
{
id: 'test-2',
description: 'Testing #2',
createdAt: new Date(),
},
{
id: 'test-3',
description: 'Testing #3',
completed: true,
createdAt: new Date(),
},
]
const { openTasks, completedTasks } = groupTasksByCompletedStatus(tasks)
expect(openTasks).toHaveLength(2)
expect(openTasks[0]).toBe(tasks[0])
expect(openTasks[1]).toBe(tasks[1])
expect(completedTasks).toHaveLength(1)
expect(completedTasks[0]).toBe(tasks[2])
})
})
describe('getTaskArrayFromGroupedTasks', () => {
it('should return an array of tasks', () => {
const workTasks = [
{
id: 'test-b-1',
description: 'Test #1',
createdAt: new Date(),
},
{
id: 'test-b-2',
description: 'Test #2',
completed: true,
createdAt: new Date(),
},
]
const personalTasks = [
{
id: 'test-c-1',
description: 'Test #3',
createdAt: new Date(),
},
{
id: 'test-c-2',
description: 'Test #4',
completed: true,
createdAt: new Date(),
},
]
const groupedTasks: GroupModel[] = [
{
name: 'Work',
sections: DEFAULT_SECTIONS,
tasks: workTasks,
},
{
name: 'Personal',
sections: DEFAULT_SECTIONS,
tasks: personalTasks,
},
]
const taskArray = getTaskArrayFromGroupedTasks(groupedTasks)
expect(taskArray).toHaveLength(workTasks.length + personalTasks.length)
expect(taskArray).toStrictEqual([...workTasks, ...personalTasks])
})
})
describe('truncateText', () => {
it('should return the text as-is', () => {
const text = 'This is a simple text. It should not be truncated.'
expect(truncateText(text, 100)).toBe(text)
})
it('should return the truncated text', () => {
const text = 'This is a simple text. It should not be truncated.'
const truncated = truncateText(text, 10)
expect(truncated).toHaveLength(13) // Includes ellipsis
expect(truncated).toBe('This is a ...')
})
})
describe('getPlainPreview', () => {
it('should return a text preview in the format: {open tasks}/{all tasks}', () => {
const workTasks = [
{
id: 'test-b-1',
description: 'Test #1',
createdAt: new Date(),
},
{
id: 'test-b-2',
description: 'Test #2',
completed: true,
createdAt: new Date(),
},
]
const personalTasks = [
{
id: 'test-c-1',
description: 'Test #3',
createdAt: new Date(),
},
{
id: 'test-c-2',
description: 'Test #4',
completed: true,
createdAt: new Date(),
},
{
id: 'test-c-3',
description: 'Test #5',
createdAt: new Date(),
},
]
const groupedTasks: GroupModel[] = [
{
name: 'Work',
sections: DEFAULT_SECTIONS,
tasks: workTasks,
},
{
name: 'Personal',
sections: DEFAULT_SECTIONS,
tasks: personalTasks,
},
]
expect(getPlainPreview(groupedTasks)).toBe('2/5 tasks completed')
expect(getPlainPreview([])).toBe('0/0 tasks completed')
expect(getPlainPreview([{ name: 'Test', tasks: [], sections: [] }])).toBe('0/0 tasks completed')
})
})
describe('parseMarkdownTasks', () => {
it('should not return tasks if payload is not in correct format', () => {
expect(parseMarkdownTasks('')).toBeUndefined()
expect(parseMarkdownTasks(' ')).toBeUndefined()
expect(parseMarkdownTasks('this is just a piece of text')).toBeUndefined()
expect(parseMarkdownTasks(undefined)).toBeUndefined()
})
it('should not return tasks without descriptions', () => {
const payload = '- [ ] '
expect(parseMarkdownTasks(payload)).toBeUndefined()
})
it('should return tasks from a payload with correct format', () => {
const payload = `- [ ] Foo
- [x] Bar
- [ ] Foobar`
expect(parseMarkdownTasks(payload)).toMatchObject<GroupModel>({
name: 'Checklist',
tasks: [
{
id: expect.any(String),
description: 'Foo',
completed: false,
createdAt: expect.any(Date),
},
{
id: expect.any(String),
description: 'Bar',
completed: true,
createdAt: expect.any(Date),
},
{
id: expect.any(String),
description: 'Foobar',
completed: false,
createdAt: expect.any(Date),
},
],
sections: DEFAULT_SECTIONS,
})
})
})
describe('arrayDefault', () => {
it('should fallback to default value', () => {
expect(arrayDefault({ defaultValue: [] })).toEqual([])
expect(arrayDefault({ value: undefined, defaultValue: [] })).toEqual([])
expect(arrayDefault({ value: [], defaultValue: ['test'] })).toEqual(['test'])
})
it('should return value', () => {
expect(arrayDefault({ value: ['test'], defaultValue: [] })).toEqual(['test'])
})
})

View File

@@ -1,144 +0,0 @@
import { v4 as uuidv4 } from 'uuid'
import { DEFAULT_SECTIONS, GroupModel, TaskModel } from '../features/tasks/tasks-slice'
export function arrayMoveMutable(array: any[], fromIndex: number, toIndex: number) {
const startIndex = fromIndex < 0 ? array.length + fromIndex : fromIndex
if (startIndex >= 0 && startIndex < array.length) {
const endIndex = toIndex < 0 ? array.length + toIndex : toIndex
const [item] = array.splice(fromIndex, 1)
array.splice(endIndex, 0, item)
}
}
export function arrayMoveImmutable(array: any[], fromIndex: number, toIndex: number) {
array = [...array]
arrayMoveMutable(array, fromIndex, toIndex)
return array
}
export function getPercentage(numberA: number, numberB: number): number {
if (numberA === 0 || numberB === 0) {
return 0
}
const min = Math.min(numberA, numberB)
const max = Math.max(numberA, numberB)
const percentage = (min / max) * 100
return Number(percentage.toFixed(2))
}
export function groupTasksByCompletedStatus(tasks: TaskModel[]) {
const openTasks = tasks.filter((task) => !task.completed)
const completedTasks = tasks.filter((task) => task.completed)
return {
openTasks,
completedTasks,
}
}
export function getTaskArrayFromGroupedTasks(groupedTasks: GroupModel[]): TaskModel[] {
let taskArray: TaskModel[] = []
groupedTasks.forEach((group) => {
taskArray = taskArray.concat(group.tasks)
})
return taskArray
}
export function truncateText(text: string, limit: number = 50) {
if (text.length <= limit) {
return text
}
return text.substring(0, limit) + '...'
}
export function getPlainPreview(groupedTasks: GroupModel[]) {
const allTasks = getTaskArrayFromGroupedTasks(groupedTasks)
const { completedTasks } = groupTasksByCompletedStatus(allTasks)
return `${completedTasks.length}/${allTasks.length} tasks completed`
}
function createTaskFromLine(rawTask: string): TaskModel | undefined {
const IS_COMPLETED = /^- \[x\] /i
const OPEN_PREFIX = '- [ ] '
const description = rawTask.replace(OPEN_PREFIX, '').replace(IS_COMPLETED, '')
if (description.length === 0) {
return
}
return {
id: uuidv4(),
description,
completed: IS_COMPLETED.test(rawTask),
createdAt: new Date(),
}
}
export function parseMarkdownTasks(payload?: string): GroupModel | undefined {
if (!payload) {
return
}
const IS_LEGACY_FORMAT = /^- \[[x ]\] .*/gim
if (!IS_LEGACY_FORMAT.test(payload)) {
return
}
const lines = payload.split('\n')
const tasks: TaskModel[] = []
lines
.filter((line) => line.replace(/ /g, '').length > 0)
.map((line) => createTaskFromLine(line))
.forEach((item) => item && tasks.push(item))
if (tasks.length === 0) {
return
}
return {
name: 'Checklist',
tasks,
sections: DEFAULT_SECTIONS,
}
}
export function isJsonString(rawString: string) {
try {
JSON.parse(rawString)
} catch (e) {
return false
}
return true
}
export function isLastActiveGroup(allGroups: GroupModel[], groupName: string): boolean {
if (allGroups.length === 0) {
return true
}
const lastActiveGroup = allGroups.reduce((prev, current) => {
if (!prev.lastActive) {
return current
}
if (!current.lastActive) {
return prev
}
return prev.lastActive > current.lastActive ? prev : current
})
return lastActiveGroup.name === groupName
}
export function arrayDefault({ value, defaultValue }: { value?: any[]; defaultValue: any[] }) {
if (!value) {
return defaultValue
}
if (value.length === 0) {
return defaultValue
}
return value
}

View File

@@ -1,53 +0,0 @@
import type { SettingsState } from './settings-slice'
import reducer, { setCanEdit, setIsRunningOnMobile, setSpellCheckerEnabled } from './settings-slice'
it('should return the initial state', () => {
return expect(
reducer(undefined, {
type: undefined,
}),
).toEqual({
canEdit: true,
isRunningOnMobile: false,
spellCheckerEnabled: true,
})
})
it('should handle setting canEdit property', () => {
const previousState: SettingsState = {
canEdit: false,
isRunningOnMobile: false,
spellCheckerEnabled: false,
}
expect(reducer(previousState, setCanEdit(true))).toEqual({
...previousState,
canEdit: true,
})
})
it('should handle setting isRunningOnMobile property', () => {
const previousState: SettingsState = {
canEdit: false,
isRunningOnMobile: false,
spellCheckerEnabled: false,
}
expect(reducer(previousState, setIsRunningOnMobile(true))).toEqual({
...previousState,
isRunningOnMobile: true,
})
})
it('should handle setting spellCheckerEnabled property', () => {
const previousState: SettingsState = {
canEdit: false,
isRunningOnMobile: false,
spellCheckerEnabled: false,
}
expect(reducer(previousState, setSpellCheckerEnabled(true))).toEqual({
...previousState,
spellCheckerEnabled: true,
})
})

Some files were not shown because too many files have changed in this diff Show More