├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
└── src
├── App.vue
├── assets
├── images
│ ├── cancel-icon.svg
│ ├── edit-icon.svg
│ ├── plus-icon.svg
│ ├── redo-icon.svg
│ ├── save-icon.svg
│ ├── to-do-list-icon.svg
│ ├── trash-icon.svg
│ └── undo-icon.svg
└── scss
│ ├── common
│ ├── button.scss
│ ├── checkbox.scss
│ ├── footer.scss
│ ├── header.scss
│ ├── input.scss
│ └── modal.scss
│ ├── components
│ └── mini-note.scss
│ ├── global.scss
│ ├── mixins.scss
│ ├── modals
│ └── comfirm.scss
│ ├── variables.scss
│ └── views
│ ├── home.scss
│ ├── not-found.scss
│ └── note.scss
├── components
├── MiniNote.vue
├── common
│ ├── Button.vue
│ ├── Footer.vue
│ ├── Header.vue
│ └── Modal.vue
└── modals
│ └── ConfirmModal.vue
├── main.js
├── router
└── index.js
├── store
├── index.js
└── types.js
└── views
├── Home.vue
├── NotFound.vue
└── Note.vue
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | end_of_line = lf
5 | trim_trailing_whitespace = true
6 | insert_final_newline = true
7 | max_line_length = 100
8 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | 'plugin:vue/essential',
8 | '@vue/airbnb'
9 | ],
10 | parserOptions: {
11 | parser: 'babel-eslint'
12 | },
13 | rules: {
14 | 'func-names': ['error', 'never'],
15 | 'arrow-parens': ['error', 'as-needed'],
16 | semi: ['error', 'never'],
17 | 'import/extensions': [2, 'never'],
18 | 'comma-dangle': ['error', 'never'],
19 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # test-todo
2 |
3 | Приложение состоит всего из 2х страниц.
4 |
5 | На главной странице отображается список всех заметок.
6 | Для каждой заметки отображается заголовок и Todo, сокращенный до нескольких пунктов, без возможности отмечать.
7 | Действия на главной:
8 | - перейти к созданию новой заметки
9 | - перейти к изменению
10 | - удалить (необходимо подтверждение)
11 |
12 | Страница изменения заметки позволяет определенную заметку отредактировать, отметить пункты Todo, а после сохранить изменения.
13 | Действия с заметкой:
14 | - сохранить изменения
15 | - отменить редактирование (необходимо подтверждение)
16 | - удалить (необходимо подтверждение)
17 | - отменить внесенное изменение
18 | - повторить отмененное изменение
19 | Действия с пунктами Todo:
20 | - добавить
21 | - удалить
22 | - отредактировать текст
23 | - отметить как выполненный
24 |
25 | Требования к функционалу:
26 | - Все действия на сайте должны происходить без перезагрузки страницы.
27 | - Подтверждение действий (удалить заметку) выполняется с помощью диалогового окна.
28 | - Интерфейс должен отвечать требованиям usability.
29 | - После перезагрузки страницы состояние списка заметок должно сохраняться.
30 | - Можно пренебречь несоответствием редактирования текста с помощью кнопок отменить/повторить и аналогичным действиям с помощью комбинаций клавиш (Ctrl+Z, Command+Z, etc.).
31 |
32 | Технические требования:
33 | - Диалоговые окна должны быть реализованы без использования "alert", "prompt" и "confirm".
34 | - В качестве языка разработки допускается использовать JavaScript или TypeScript.
35 | - В качестве сборщика, если это необходимо, используйте Webpack.
36 | - Верстка должна быть выполнена без использования UI библиотек (например Vuetify).
37 | - Адаптивность не обязательна, но приветствуется.
38 | - Логика приложения должна быть разбита на разумное количество самодостаточных Vue-компонентов.
39 |
40 | ## Project setup
41 | ```
42 | npm install
43 | ```
44 |
45 | ### Compiles and hot-reloads for development
46 | ```
47 | npm run serve
48 | ```
49 |
50 | ### Compiles and minifies for production
51 | ```
52 | npm run build
53 | ```
54 |
55 | ### Lints and fixes files
56 | ```
57 | npm run lint
58 | ```
59 |
60 | ### Customize configuration
61 | See [Configuration Reference](https://cli.vuejs.org/config/).
62 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-todo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "npm run serve",
7 | "serve": "vue-cli-service serve",
8 | "build": "vue-cli-service build",
9 | "lint": "vue-cli-service lint"
10 | },
11 | "dependencies": {
12 | "core-js": "^3.6.5",
13 | "debounce": "^1.2.0",
14 | "normalize.css": "^8.0.1",
15 | "uuid": "^8.2.0",
16 | "vue": "^2.6.11",
17 | "vue-router": "^3.2.0",
18 | "vuex": "^3.4.0"
19 | },
20 | "devDependencies": {
21 | "@vue/cli-plugin-babel": "~4.4.0",
22 | "@vue/cli-plugin-eslint": "~4.4.0",
23 | "@vue/cli-plugin-router": "~4.4.0",
24 | "@vue/cli-plugin-vuex": "~4.4.0",
25 | "@vue/cli-service": "~4.4.0",
26 | "@vue/eslint-config-airbnb": "^5.0.2",
27 | "babel-eslint": "^10.1.0",
28 | "eslint": "^6.7.2",
29 | "eslint-plugin-import": "^2.20.2",
30 | "eslint-plugin-vue": "^6.2.2",
31 | "node-sass": "^4.12.0",
32 | "sass-loader": "^8.0.2",
33 | "vue-template-compiler": "^2.6.11"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dDenysS/vue-todo/d99db9b8a1e7a351368f868d0130a3fdc8b988f3/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | <%= htmlWebpackPlugin.options.title %>
10 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
21 |
--------------------------------------------------------------------------------
/src/assets/images/cancel-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/edit-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/plus-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/redo-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/src/assets/images/save-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/src/assets/images/to-do-list-icon.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/images/trash-icon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/images/undo-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/src/assets/scss/common/button.scss:
--------------------------------------------------------------------------------
1 | @import "../variables";
2 | @import "../mixins";
3 |
4 | .button {
5 | display: flex;
6 | align-items: center;
7 | text-transform: uppercase;
8 | font-weight: bold;
9 | background-color: #212121;
10 | color: #b0b1aa;
11 | margin: 5px;
12 | padding: 5px 10px;
13 | border-radius: 10px;
14 | line-height: 1;
15 | outline: none;
16 | border: 1px solid transparent;
17 |
18 | &:hover {
19 | cursor: pointer;
20 | @include box-shadow();
21 | }
22 |
23 | &:focus {
24 | border-color: $white;
25 | }
26 |
27 | &:active {
28 | opacity: .8;
29 | }
30 |
31 | &:disabled {
32 | opacity: .8;
33 | box-shadow: none;
34 | background-color: #ccc;
35 | }
36 | }
37 |
38 | .button__icon {
39 | width: 20px;
40 | margin:0 2.5px;
41 | height: auto;
42 | }
43 |
44 | .button__text {
45 | margin:0 2.5px;
46 | padding-top: 3px;
47 | }
48 |
--------------------------------------------------------------------------------
/src/assets/scss/common/checkbox.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | .custom-checkbox {
4 | display: none;
5 | }
6 |
7 | .custom-checkbox + label {
8 | display: block;
9 | position: relative;
10 | width: 23px;
11 | min-width: 23px;
12 | height: 20px;
13 | font: 14px/20px "Open Sans", Arial, sans-serif;
14 | color: #ddd;
15 | cursor: pointer;
16 | -webkit-user-select: none;
17 | -moz-user-select: none;
18 | -ms-user-select: none;
19 | }
20 |
21 | .custom-checkbox:disabled + label {
22 | cursor: unset;
23 | }
24 |
25 | .custom-checkbox + label:last-child {
26 | margin-bottom: 0;
27 | }
28 |
29 | .custom-checkbox + label:before {
30 | content: "";
31 | display: block;
32 | width: 20px;
33 | height: 20px;
34 | border: 1px solid #6cc0e5;
35 | position: absolute;
36 | left: 0;
37 | top: 0;
38 | opacity: .6;
39 | -webkit-transition: all .12s, border-color .08s;
40 | transition: all .12s, border-color .08s;
41 | }
42 |
43 | .custom-checkbox:checked + label:before {
44 | width: 10px;
45 | top: -5px;
46 | left: 5px;
47 | border-radius: 0;
48 | opacity: 1;
49 | border-top-color: transparent;
50 | border-left-color: transparent;
51 | -webkit-transform: rotate(45deg);
52 | transform: rotate(45deg);
53 | }
54 |
--------------------------------------------------------------------------------
/src/assets/scss/common/footer.scss:
--------------------------------------------------------------------------------
1 | @import "../mixins";
2 |
3 | .footer {
4 | box-shadow: 0 -1px 10px 0 rgb(71, 71, 71);
5 | background-color: #272727;
6 | margin-top: 40px;
7 | }
8 |
9 | .footer__wrapper {
10 | display: flex;
11 | justify-content: space-between;
12 | flex-direction: column;
13 | align-items: center;
14 |
15 | @include media-tablet {
16 | flex-direction: row;
17 | }
18 | }
19 |
20 | .footer__paragraph {
21 | max-width: 300px;
22 | }
23 |
--------------------------------------------------------------------------------
/src/assets/scss/common/header.scss:
--------------------------------------------------------------------------------
1 | @import "../variables";
2 | @import "../mixins";
3 |
4 | .header {
5 | @include box-shadow(0, 1px);
6 | background-color: $black;
7 | }
8 |
9 | .logo {
10 | display: inline-flex;
11 | align-items: center;
12 |
13 | border-bottom: 2px solid transparent;
14 |
15 | &:hover {
16 | border-color: $white;
17 | }
18 |
19 | &:active {
20 | opacity: .7;
21 | }
22 | }
23 |
24 | .logo__icon {
25 | width: 40px;
26 | margin-right: 10px;
27 | }
28 |
29 | .logo__name {
30 | font-weight: bold;
31 | color: $white;
32 | font-size: 40px;
33 | }
34 |
--------------------------------------------------------------------------------
/src/assets/scss/common/input.scss:
--------------------------------------------------------------------------------
1 | @import "../variables";
2 |
3 | .custom-input {
4 | margin: 5px;
5 | }
6 |
7 | .custom-input__description {
8 | margin-right: 15px;
9 | }
10 |
11 | .custom-input__field {
12 | border-width: 0;
13 | background-color: #302c2e;
14 |
15 | padding: 5px 5px 2px;
16 | color: #f5f5f5;
17 |
18 | border-bottom: 2px solid #3D3837;
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/src/assets/scss/common/modal.scss:
--------------------------------------------------------------------------------
1 | .modal {
2 | position: fixed;
3 | top: 0;
4 | right: 0;
5 |
6 | width: 100vw;
7 | height: 100vh;
8 |
9 | background-color: rgba(0, 0, 0, .7);
10 |
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | }
15 |
16 | .modal__content {
17 | width: 100%;
18 | border: 2px solid #262120;
19 | background-color: #3d3837;
20 | max-width: 600px;
21 | padding: 10px;
22 | }
23 |
--------------------------------------------------------------------------------
/src/assets/scss/components/mini-note.scss:
--------------------------------------------------------------------------------
1 | @import "../variables";
2 | @import "../mixins";
3 |
4 | .mini-note {
5 | display: flex;
6 | flex-direction: column;
7 | width: 100%;
8 | margin-bottom: 4%;
9 | margin-right: 2%;
10 | margin-left: 2%;
11 | padding: 15px 10px;
12 | background-color: $black;
13 | @include box-shadow();
14 |
15 | @include media-tablet {
16 | max-width: 46%;
17 | }
18 | }
19 |
20 | .mini-note__title {
21 | margin: 0;
22 | }
23 |
24 | .mini-note__items {
25 | list-style: none;
26 | padding-left: 10px;
27 | position: relative;
28 | margin: 15px 0 10px;
29 | }
30 |
31 | .mini-note__items--more:before {
32 | content: "";
33 | position: absolute;
34 |
35 | bottom: 0;
36 | left: 0;
37 | width: 100%;
38 | height: .1px;
39 |
40 | box-shadow: #d6bfbf -1px -2px 4px 0px;
41 | }
42 |
43 | .mini-note__item {
44 | display: flex;
45 | margin-bottom: 10px;
46 | }
47 |
48 | .mini-note__actions {
49 | margin-top: auto;
50 | display: flex;
51 | justify-content: flex-end;
52 | }
53 |
--------------------------------------------------------------------------------
/src/assets/scss/global.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 | @import "~normalize.css/normalize";
3 | @import "common/checkbox";
4 | @import "common/input";
5 |
6 | html {
7 | box-sizing: border-box;
8 | }
9 |
10 | *, *::before, *::after {
11 | box-sizing: inherit;
12 | }
13 |
14 | body {
15 | margin: 0;
16 | background-color: #111;
17 | color: $white;
18 | font-family: Roboto, Arial, sans-serif;
19 | }
20 |
21 | img {
22 | max-width: 100%;
23 | height: auto;
24 | }
25 |
26 | a {
27 | text-decoration: none
28 | }
29 |
30 | #app {
31 | min-height: 100vh;
32 | display: flex;
33 | flex-direction: column;
34 | justify-content: space-between;
35 | }
36 |
37 | .container {
38 | max-width: 1200px;
39 | width: 100%;
40 | margin: 0 auto;
41 | flex: 1;
42 | padding: 10px;
43 | }
44 |
--------------------------------------------------------------------------------
/src/assets/scss/mixins.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | @mixin box-shadow($offsetX:0, $offsetY:0) {
4 | box-shadow: $offsetX $offsetY 10px 0 rgb(71, 71, 71);
5 | }
6 |
7 | @mixin media-tablet {
8 | @media (min-width: $mediaWidthTablet) {
9 | @content;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/assets/scss/modals/comfirm.scss:
--------------------------------------------------------------------------------
1 | .confirm-modal__title {
2 | text-align: center;
3 | }
4 |
5 | .confirm-modal__actions {
6 | display: flex;
7 | justify-content: center;
8 | }
9 |
--------------------------------------------------------------------------------
/src/assets/scss/variables.scss:
--------------------------------------------------------------------------------
1 | $black: #272727;
2 | $white: #f5f5f5;
3 |
4 | // Mobile first
5 | $mediaWidthTablet: 768px;
6 |
--------------------------------------------------------------------------------
/src/assets/scss/views/home.scss:
--------------------------------------------------------------------------------
1 | @import "../mixins";
2 |
3 | .head {
4 | display: flex;
5 | align-content: center;
6 | justify-content: space-between;
7 | flex-direction: column;
8 | margin-top: 20px;
9 | margin-bottom: 30px;
10 |
11 | @include media-tablet {
12 | flex-direction: row;
13 | }
14 | }
15 |
16 | .note-list {
17 | display: flex;
18 | flex-wrap: wrap;
19 | margin: 0 -2%;
20 | }
21 |
--------------------------------------------------------------------------------
/src/assets/scss/views/not-found.scss:
--------------------------------------------------------------------------------
1 | .not-found {
2 | overflow: hidden;
3 | position: relative;
4 | flex: 1;
5 | }
6 |
7 | .not-found__error {
8 | font-size: 95px;
9 | width: 100px;
10 | height: 60px;
11 | line-height: 60px;
12 | margin: auto;
13 | position: absolute;
14 | top: 0;
15 | bottom: 0;
16 | left: -60px;
17 | right: 0;
18 | animation: noise 2s linear infinite;
19 | }
20 |
21 |
22 | .not-found__error:after {
23 | content: "404";
24 | font-size: 100px;
25 | font-style: italic;
26 | text-align: center;
27 | width: 150px;
28 | height: 60px;
29 | line-height: 60px;
30 | margin: auto;
31 | position: absolute;
32 | top: 0;
33 | bottom: 0;
34 | left: 0;
35 | right: 0;
36 | opacity: 0;
37 | color: blue;
38 | animation: noise-1 .2s linear infinite;
39 | }
40 |
41 | .not-found__error:before {
42 | content: "404";
43 | font-size: 100px;
44 | font-style: italic;
45 | text-align: center;
46 | width: 100px;
47 | height: 60px;
48 | line-height: 60px;
49 | margin: auto;
50 | position: absolute;
51 | top: 0;
52 | bottom: 0;
53 | left: 0;
54 | right: 0;
55 | opacity: 0;
56 | color: red;
57 | animation: noise-2 .2s linear infinite;
58 | }
59 |
60 | .not-found__info {
61 | text-align: center;
62 | font-size: 15px;
63 | font-style: italic;
64 | width: 200px;
65 | height: 60px;
66 | line-height: 60px;
67 | margin: auto;
68 | position: absolute;
69 | top: 140px;
70 | bottom: 0;
71 | left: 0;
72 | right: 0;
73 | animation: noise-3 1s linear infinite;
74 | }
75 |
76 | @keyframes noise-1 {
77 | 0%, 20%, 40%, 60%, 70%, 90% {
78 | opacity: 0;
79 | }
80 | 10% {
81 | opacity: .1;
82 | }
83 | 50% {
84 | opacity: .5;
85 | left: -6px;
86 | }
87 | 80% {
88 | opacity: .3;
89 | }
90 | 100% {
91 | opacity: .6;
92 | left: 2px;
93 | }
94 | }
95 |
96 | @keyframes noise-2 {
97 | 0%, 20%, 40%, 60%, 70%, 90% {
98 | opacity: 0;
99 | }
100 | 10% {
101 | opacity: .1;
102 | }
103 | 50% {
104 | opacity: .5;
105 | left: 6px;
106 | }
107 | 80% {
108 | opacity: .3;
109 | }
110 | 100% {
111 | opacity: .6;
112 | left: -2px;
113 | }
114 | }
115 |
116 | @keyframes noise {
117 | 0%, 3%, 5%, 42%, 44%, 100% {
118 | opacity: 1;
119 | transform: scaleY(1);
120 | }
121 | 4.3% {
122 | opacity: 1;
123 | transform: scaleY(1.7);
124 | }
125 | 43% {
126 | opacity: 1;
127 | transform: scaleX(1.5);
128 | }
129 | }
130 |
131 | @keyframes noise-3 {
132 | 0%, 3%, 5%, 42%, 44%, 100% {
133 | opacity: 1;
134 | transform: scaleY(1);
135 | }
136 | 4.3% {
137 | opacity: 1;
138 | transform: scaleY(4);
139 | }
140 | 43% {
141 | opacity: 1;
142 | transform: scaleX(10) rotate(60deg);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/assets/scss/views/note.scss:
--------------------------------------------------------------------------------
1 | @import "../variables";
2 | @import "../mixins";
3 |
4 | .note {
5 | margin-top: 40px;
6 | }
7 |
8 | .note__wrapper {
9 | background-color: $black;
10 | @include box-shadow()
11 | }
12 |
13 | .todo {
14 | display: flex;
15 | align-items: center;
16 | margin: 5px 0;
17 | }
18 |
19 | .note__actions {
20 | margin-top: 30px;
21 | display: flex;
22 | align-items: center;
23 |
24 | flex-wrap: wrap;
25 |
26 | @include media-tablet {
27 | justify-content: flex-end;
28 | }
29 | }
30 |
31 | .note__action--add-task {
32 | margin-right: auto;
33 | }
34 |
35 | .note__action--mini {
36 | font-size: 14px;
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/MiniNote.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ note.name ? note.name : 'Title is empty :(' }}
4 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
23 |
65 |
66 |
69 |
--------------------------------------------------------------------------------
/src/components/common/Button.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | {{ text}}
8 |
9 |
13 |
14 |
15 |
44 |
45 |
48 |
--------------------------------------------------------------------------------
/src/components/common/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
20 |
21 |
24 |
--------------------------------------------------------------------------------
/src/components/common/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
17 |
18 |
21 |
--------------------------------------------------------------------------------
/src/components/common/Modal.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
20 |
21 |
24 |
--------------------------------------------------------------------------------
/src/components/modals/ConfirmModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ text }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
39 |
40 |
43 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import App from '@/App'
4 |
5 | import router from '@/router'
6 | import store from '@/store'
7 |
8 | import '@/assets/scss/global.scss'
9 |
10 | Vue.config.productionTip = false
11 |
12 | new Vue({
13 | router,
14 | store,
15 | render: h => h(App)
16 | }).$mount('#app')
17 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 |
4 | import Home from '../views/Home'
5 |
6 | Vue.use(VueRouter)
7 |
8 | const routes = [
9 | {
10 | path: '/',
11 | name: 'Home',
12 | component: Home
13 | },
14 | {
15 | path: '/notes',
16 | redirect: { name: 'Home' }
17 | },
18 | {
19 | path: '/notes/create',
20 | name: 'NoteCreate',
21 | component: () => import('../views/Note')
22 | },
23 | {
24 | path: '/notes/:id',
25 | name: 'Note',
26 | component: () => import('../views/Note')
27 | },
28 | {
29 | path: '/404',
30 | name: 'NotFound',
31 | component: () => import('../views/NotFound')
32 | },
33 | {
34 | path: '*',
35 | redirect: { name: 'NotFound' }
36 | }
37 | ]
38 |
39 | const router = new VueRouter({
40 | mode: 'history',
41 | base: process.env.BASE_URL,
42 | routes
43 | })
44 |
45 | export default router
46 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | import types from '@/store/types'
5 |
6 | Vue.use(Vuex)
7 |
8 | const notesLocalStorage = localStorage.getItem('notes')
9 |
10 | export default new Vuex.Store({
11 | state: {
12 | notes: notesLocalStorage ? JSON.parse(notesLocalStorage) : []
13 | },
14 | getters: {
15 | getNoteById: state => id => state.notes.find(note => note.id === id)
16 | },
17 | mutations: {
18 | [types.ADD_NOTE](state, { note }) {
19 | state.notes.push(note)
20 | localStorage.setItem('notes', JSON.stringify(state.notes))
21 | },
22 | [types.EDIT_NOTE](state, { note }) {
23 | const notes = state.notes.slice()
24 | const index = notes.findIndex(item => item.id === note.id)
25 |
26 | notes[index] = note
27 |
28 | state.notes = notes
29 | localStorage.setItem('notes', JSON.stringify(state.notes))
30 | },
31 | [types.REMOVE_NOTE](state, { id }) {
32 | state.notes = state.notes.filter(note => note.id !== id)
33 | localStorage.setItem('notes', JSON.stringify(state.notes))
34 | }
35 | }
36 | })
37 |
--------------------------------------------------------------------------------
/src/store/types.js:
--------------------------------------------------------------------------------
1 | const types = {
2 | ADD_NOTE: 'ADD_NOTE',
3 | EDIT_NOTE: 'EDIT_NOTE',
4 | REMOVE_NOTE: 'REMOVE_NOTE'
5 | }
6 | export default types
7 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Список заметок
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
37 |
38 |
41 |
--------------------------------------------------------------------------------
/src/views/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
404
5 |
Page not found
6 |
7 |
8 |
9 |
10 |
15 |
16 |
19 |
--------------------------------------------------------------------------------
/src/views/Note.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ isCreateMode ? 'Создание': 'Редактирование'}} заметки
5 |
6 |
49 |
50 |
52 |
56 |
57 |
58 |
59 |
217 |
218 |
221 |
--------------------------------------------------------------------------------