├── master
├── CNAME
├── public
├── favicon.ico
├── manifest.json
└── index.html
├── src
├── utils
│ └── storage.js
├── constants
│ └── index.js
├── components
│ ├── Head.js
│ ├── ListItem.js
│ ├── List.js
│ ├── Preview.js
│ ├── Layer.js
│ ├── Edit.js
│ ├── App.js
│ └── App.css
├── index.js
├── containers
│ ├── ContainerList.js
│ ├── ContainerLayer.js
│ ├── ContainerPreview.js
│ └── ContainerEdit.js
├── index.css
├── actions
│ └── note.js
├── registerServiceWorker.js
└── reducers
│ └── index.js
├── README.md
├── package.json
├── LICENSE
└── .gitignore
/master:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | yunbi.txliang.com
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seymoe/yunbi-notes/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/utils/storage.js:
--------------------------------------------------------------------------------
1 | export const setStorage = (key, value) => {
2 | if (!window.localStorage) return alert('很抱歉,您的客户端不支持本地存储。')
3 | localStorage.setItem(key, value)
4 | }
5 |
6 | export const getStorage = (key) => {
7 | return localStorage.getItem(key)
8 | }
--------------------------------------------------------------------------------
/src/constants/index.js:
--------------------------------------------------------------------------------
1 | export const ADD_NOTE = 'ADD_NOTE'
2 | export const DELETE_NOTE = 'DELETE_NOTE'
3 | export const SHOW_NOTE = 'SHOW_NOTE'
4 | //export const MODIFY_NOTE = 'MODIFY_NOTE'
5 | //export const SAVE_NOTE = 'SAVE_NOTE'
6 | export const SHOW_LAYER = 'SHOW_LAYER'
7 | export const SHOW_EDITER = 'SHOW_EDITER'
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/Head.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Head = ({isFullScreen}) => {
4 | return (
5 |
12 | )
13 | }
14 |
15 | export default Head
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 芸笔
2 |
3 | ### 基于create-react-app, React+Redux构建的记事本类应用
4 | ### 在线预览地址:[芸笔](http://yunbi.txliang.com/)
5 |
6 | 别人老是推荐你写个todolist,是不是觉得听的疲劳了?那来写个记事本吧~
7 |
8 | `git clone https://github.com/ximolang/yunbi-notes.git`
9 |
10 | ### `npm start`
11 | 启动预览
12 |
13 | ### `npm run build`
14 | 构建生产环境文件
15 |
16 | ### 使用gh-pages进行预览
17 | 若不存在gh-pages分支,则可以直接执行命令
18 | ```
19 | git subtree push --prefix=build origin gh-pages
20 | ```
21 | 若存在,则可以在github上删掉此分支再执行上一条命令,其他的方式没踩过坑,这种方式暴力有效。
--------------------------------------------------------------------------------
/src/components/ListItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const ListItem = ({ id, title, content, time, clickPreview, changeStatusShow, showNoteDetail, cls}) => {
4 | return (
5 |
{
6 | clickPreview(id)
7 | changeStatusShow(!showNoteDetail)
8 | }}>
9 |
10 |
{title}
11 |
{content}
12 |
{time}
13 |
14 |
15 | )
16 | }
17 |
18 | export default ListItem
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { createStore } from 'redux'
4 | import {Provider } from 'react-redux'
5 | import App from './components/App'
6 |
7 | import reducer from './reducers'
8 |
9 | import registerServiceWorker from './registerServiceWorker'
10 | import './index.css'
11 |
12 | let store = createStore(reducer)
13 |
14 | console.log(store.getState())
15 |
16 | ReactDOM.render(
17 |
18 |
19 |
20 | , document.getElementById('root'))
21 | registerServiceWorker()
22 |
--------------------------------------------------------------------------------
/src/containers/ContainerList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import List from '../components/List'
3 | import { showNote, showLayer } from '../actions/note'
4 |
5 | const mapStateToProps = (state, ownProps) => ({
6 | listData: state.notes,
7 | isShowLayer: state.isShowLayer
8 | })
9 |
10 | const mapDispatchToProps = (dispatch, ownProps) => ({
11 | onClick: (id) => {
12 | dispatch(showNote(id))
13 | },
14 | addBtnClick: (isSL) => {
15 | dispatch(showLayer(isSL))
16 | }
17 | })
18 |
19 | const ContainerList = connect(
20 | mapStateToProps,
21 | mapDispatchToProps
22 | )(List)
23 |
24 | export default ContainerList
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yunbi",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "github-markdown-css": "^2.6.0",
7 | "highlight.js": "^9.12.0",
8 | "marked": "^0.3.6",
9 | "moment": "^2.18.1",
10 | "react": "^15.5.4",
11 | "react-dom": "^15.5.4",
12 | "react-redux": "^5.0.5",
13 | "redux": "^3.6.0"
14 | },
15 | "devDependencies": {
16 | "react-scripts": "1.0.7"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test --env=jsdom",
22 | "eject": "react-scripts eject"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/containers/ContainerLayer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import Layer from '../components/Layer'
3 | import { addNote, showLayer } from '../actions/note'
4 |
5 | const mapStateToProps = (state, ownProps) => ({
6 | isShowLayer: state.isShowLayer
7 | })
8 |
9 | const mapDispatchToProps = (dispatch, ownProps) => ({
10 | addNewClick: (title, content) => {
11 | dispatch(addNote(title, content))
12 | dispatch(showLayer(false))
13 | },
14 | closeLayerWrap: () => dispatch(showLayer(false))
15 | })
16 |
17 | const ContainerLayer = connect(
18 | mapStateToProps,
19 | mapDispatchToProps
20 | )(Layer)
21 |
22 | export default ContainerLayer
--------------------------------------------------------------------------------
/src/containers/ContainerPreview.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import Preview from '../components/Preview'
3 | import { deleteNote, showEditer } from '../actions/note'
4 |
5 | const mapStateToProps = (state, ownProps) => ({
6 | currentNote: state.cnote,
7 | isShowEditer: state.isShowEditer
8 | })
9 |
10 | const mapDispatchToProps = (dispatch, ownProps) => ({
11 | editClick: (isSE) => {
12 | dispatch(showEditer(isSE))
13 | },
14 | deleteClick: (id) => {
15 | dispatch(deleteNote(id))
16 | }
17 | })
18 |
19 | const ContainerPreview = connect(
20 | mapStateToProps,
21 | mapDispatchToProps
22 | )(Preview)
23 |
24 | export default ContainerPreview
--------------------------------------------------------------------------------
/src/containers/ContainerEdit.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import Edit from '../components/Edit'
3 | import { addNote, showEditer } from '../actions/note'
4 |
5 | const mapStateToProps = (state, ownProps) => ({
6 | isShowEditer: state.isShowEditer,
7 | id: state.cnote.id,
8 | title: state.cnote.title,
9 | content: state.cnote.content
10 | })
11 |
12 | const mapDispatchToProps = (dispatch, ownProps) => ({
13 | addNewClick: (title, content, id) => {
14 | dispatch(addNote(title, content, id))
15 | dispatch(showEditer(false))
16 | },
17 | closeLayerWrap: () => dispatch(showEditer(false))
18 | })
19 |
20 | const ContainerEditer = connect(
21 | mapStateToProps,
22 | mapDispatchToProps
23 | )(Edit)
24 |
25 | export default ContainerEditer
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Ximolang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 |
--------------------------------------------------------------------------------
/src/components/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ListItem from './ListItem'
3 |
4 | const List = ({ listData, isShowLayer, showNoteDetail, changeStatusShow, onClick, addBtnClick }) => {
5 | let showDetailCls = showNoteDetail ? 'toLeft' : 'toRight'
6 | let hasNoteToggle
7 | if (listData.length === 0) {
8 | hasNoteToggle = 你还没有动过笔呐~
9 | } else {
10 | hasNoteToggle =
11 | {listData.map((item) => {
12 | const cls = (item.isActive) ? 'active' : ''
13 | return (
14 | 80) ? (item.content.substring(0, 80)+'...') : item.content} time={item.time} cls={cls} showNoteDetail={showNoteDetail} changeStatusShow={changeStatusShow} clickPreview={onClick} />
15 | )
16 | })
17 | }
18 |
19 | }
20 | return (
21 |
22 |
所有笔记
23 | addBtnClick(!isShowLayer)}>
24 | {hasNoteToggle}
25 |
26 | )
27 | }
28 |
29 | export default List
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | *{
2 | margin: 0;
3 | padding: 0;
4 | }
5 | html{
6 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif;
7 | font-size: 14px;
8 | line-height: 1.5;
9 | -ms-text-size-adjust: 100%;
10 | -webkit-text-size-adjust: 100%;
11 | }
12 | a{
13 | background-color: transparent;
14 | -webkit-text-decoration-skip: objects;
15 | }
16 | *:active{
17 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
18 | }
19 |
20 | @font-face {
21 | font-family: 'iconfont'; /* project id 267234 */
22 | src: url('//at.alicdn.com/t/font_bpfbfrt4hdea5rk9.eot');
23 | src: url('//at.alicdn.com/t/font_bpfbfrt4hdea5rk9.eot?#iefix') format('embedded-opentype'),
24 | url('//at.alicdn.com/t/font_bpfbfrt4hdea5rk9.woff') format('woff'),
25 | url('//at.alicdn.com/t/font_bpfbfrt4hdea5rk9.ttf') format('truetype'),
26 | url('//at.alicdn.com/t/font_bpfbfrt4hdea5rk9.svg#iconfont') format('svg');
27 | }
28 |
29 | .iconfont{
30 | font-family:"iconfont" !important;
31 | font-size:16px;font-style:normal;
32 | -webkit-font-smoothing: antialiased;
33 | -webkit-text-stroke-width: 0.2px;
34 | -moz-osx-font-smoothing: grayscale;
35 | }
--------------------------------------------------------------------------------
/src/components/Preview.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | const marked = require('marked')
3 |
4 | export default class Preview extends Component {
5 | render() {
6 | let showDetailCls = this.props.showNoteDetail ? 'toLeft' : 'toRight'
7 | let isFullScreen = this.props.isFullScreen
8 | return (
9 |
28 | )
29 | }
30 | }
--------------------------------------------------------------------------------
/src/actions/note.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment'
2 | import { getStorage } from '../utils/storage'
3 | import { ADD_NOTE, DELETE_NOTE, SHOW_NOTE, SHOW_LAYER, SHOW_EDITER } from '../constants'
4 |
5 | let noteId
6 | let notesArr2
7 | let notesArr = JSON.parse(getStorage('notes'))
8 |
9 | if (notesArr instanceof Array && notesArr.length !== 0) {
10 | if (notesArr.length === 1) {
11 | notesArr2 = notesArr
12 | } else {
13 | notesArr2 = notesArr.sort(function (a,b){
14 | return a.id < b.id
15 | })
16 | }
17 | noteId = notesArr2[0]['id']+1
18 | } else {
19 | noteId = 0
20 | }
21 |
22 | // 增加一篇笔记 或者 编辑之后保存
23 | export const addNote = (title, content, id, time) => {
24 | if (id === undefined && time === undefined) {
25 | return {
26 | type: ADD_NOTE,
27 | id: noteId++,
28 | title,
29 | content,
30 | time: moment().format("YYYY-MM-DD HH:mm")
31 | }
32 | } else {
33 | return {
34 | type: ADD_NOTE,
35 | id,
36 | title,
37 | content,
38 | time: moment().format("YYYY-MM-DD HH:mm")
39 | }
40 | }
41 | }
42 |
43 | // 删除一篇笔记
44 | export const deleteNote = (id) => {
45 | return {
46 | type: DELETE_NOTE,
47 | id
48 | }
49 | }
50 |
51 | // 预览一篇笔记
52 | export const showNote = (id) => {
53 | return {
54 | type: SHOW_NOTE,
55 | id
56 | }
57 | }
58 |
59 | // 显示浮出层
60 | export const showLayer = (isShowLayer) => {
61 | return {
62 | type: SHOW_LAYER,
63 | isShowLayer
64 | }
65 | }
66 |
67 | // 显示编辑浮出层
68 | export const showEditer = (isShowEditer) => {
69 | return {
70 | type: SHOW_EDITER,
71 | isShowEditer
72 | }
73 | }
--------------------------------------------------------------------------------
/src/components/Layer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | const marked = require('marked')
3 |
4 | class Layer extends React.Component {
5 | constructor(props) {
6 | super(props)
7 | this.state = {
8 | title: '',
9 | content: ''
10 | }
11 | this.saveANote = this.saveANote.bind(this)
12 | this.changeTitle = this.changeTitle.bind(this)
13 | this.changeContent = this.changeContent.bind(this)
14 | }
15 |
16 | saveANote (tit, ctt) {
17 | if (!tit.trim() || !ctt.trim()) return
18 | this.props.addNewClick(tit, ctt)
19 | }
20 |
21 | changeTitle(e) {
22 | this.setState({
23 | title: e.target.value
24 | })
25 | }
26 |
27 | changeContent(e) {
28 | this.setState({
29 | content: e.target.value
30 | })
31 | }
32 |
33 | render() {
34 | let markedString = `# ${this.state.title}\n\n${this.state.content}`
35 | return (
36 |
37 |
38 |
39 |
新增
40 |
this.saveANote(this.state.title, this.state.content)}>
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
53 |
54 |
55 | )
56 | }
57 | }
58 |
59 | export default Layer
--------------------------------------------------------------------------------
/src/components/Edit.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | const marked = require('marked')
3 |
4 | class Edit extends React.Component {
5 | constructor(props) {
6 | super(props)
7 | const _t = this.props.title
8 | const _c = this.props.content
9 | this.state = {
10 | title: _t,
11 | content: _c
12 | }
13 | this.saveANote = this.saveANote.bind(this)
14 | this.changeTitle = this.changeTitle.bind(this)
15 | this.changeContent = this.changeContent.bind(this)
16 | }
17 |
18 | saveANote(tit, ctt, id) {
19 | if (!tit.trim() || !ctt.trim()) return
20 | this.props.addNewClick(tit, ctt, id)
21 | }
22 |
23 | changeTitle(e) {
24 | this.setState({
25 | title: e.target.value
26 | })
27 | }
28 |
29 | changeContent(e) {
30 | this.setState({
31 | content: e.target.value
32 | })
33 | }
34 |
35 | render() {
36 | let markedString = `# ${this.state.title}\n\n${this.state.content}`
37 | return (
38 |
39 |
40 |
41 |
编辑
42 |
this.saveANote(this.state.title, this.state.content, this.props.id)}>
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
55 |
56 |
57 | )
58 | }
59 | }
60 |
61 | export default Edit
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import Head from './Head'
5 | import ContainerList from '../containers/ContainerList'
6 | import ContainerPreview from '../containers/ContainerPreview'
7 | import ContainerLayer from '../containers/ContainerLayer'
8 | import ContainerEdit from '../containers/ContainerEdit'
9 | import '../../node_modules/github-markdown-css/github-markdown.css'
10 | import './App.css';
11 |
12 |
13 | class Root extends React.Component {
14 | constructor(props) {
15 | super(props)
16 | this.state = {
17 | showNoteDetail: false,
18 | isFullScreen: false
19 | }
20 | this.changeStatusShow = this.changeStatusShow.bind(this)
21 | this.changeFullScreen = this.changeFullScreen.bind(this)
22 | }
23 |
24 | changeStatusShow(status) {
25 | this.setState({
26 | showNoteDetail: status
27 | })
28 | }
29 |
30 | changeFullScreen(status) {
31 | this.setState({
32 | isFullScreen: status
33 | })
34 | }
35 |
36 | render() {
37 | const layer = this.props.isShowLayer ? : ''
38 | const editer = this.props.isShowEditer ? : ''
39 | return (
40 |
41 |
42 |
43 |
44 |
48 |
49 | {layer}
50 | {editer}
51 |
52 | )
53 | }
54 | }
55 |
56 | const mapStateToProps = (state, ownProps) => ({
57 | isShowLayer: state.isShowLayer,
58 | isShowEditer: state.isShowEditer
59 | })
60 |
61 | const App = connect(
62 | mapStateToProps
63 | )(Root)
64 |
65 | export default App
66 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
29 | 芸笔
30 |
31 |
32 |
35 |
36 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | export default function register() {
12 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
13 | window.addEventListener('load', () => {
14 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
15 | navigator.serviceWorker
16 | .register(swUrl)
17 | .then(registration => {
18 | registration.onupdatefound = () => {
19 | const installingWorker = registration.installing;
20 | installingWorker.onstatechange = () => {
21 | if (installingWorker.state === 'installed') {
22 | if (navigator.serviceWorker.controller) {
23 | // At this point, the old content will have been purged and
24 | // the fresh content will have been added to the cache.
25 | // It's the perfect time to display a "New content is
26 | // available; please refresh." message in your web app.
27 | console.log('New content is available; please refresh.');
28 | } else {
29 | // At this point, everything has been precached.
30 | // It's the perfect time to display a
31 | // "Content is cached for offline use." message.
32 | console.log('Content is cached for offline use.');
33 | }
34 | }
35 | };
36 | };
37 | })
38 | .catch(error => {
39 | console.error('Error during service worker registration:', error);
40 | });
41 | });
42 | }
43 | }
44 |
45 | export function unregister() {
46 | if ('serviceWorker' in navigator) {
47 | navigator.serviceWorker.ready.then(registration => {
48 | registration.unregister();
49 | });
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { ADD_NOTE, DELETE_NOTE, SHOW_NOTE, SHOW_LAYER, SHOW_EDITER } from '../constants'
2 | import {setStorage, getStorage} from '../utils/storage'
3 |
4 | // 获取初始数据
5 | const localnotes = JSON.parse(getStorage('notes'))
6 | const localcnote = getStorage('cnote')
7 | const initialState = {
8 | notes: localnotes || [],
9 | cnote: localcnote || {},
10 | isShowLayer: false,
11 | idShowEditer: false
12 | }
13 |
14 | const note = (state = {}, action) => {
15 | switch (action.type) {
16 | case ADD_NOTE:
17 | return Object.assign({}, state, {
18 | id: action.id,
19 | title: action.title,
20 | content: action.content,
21 | time: action.time
22 | })
23 | default:
24 | return state
25 | }
26 | }
27 |
28 | const notes = (state = [], action) => {
29 | switch (action.type) {
30 | case ADD_NOTE:
31 | let isNew = true
32 | const _arr = state.map((item) => {
33 | if (item.id === action.id) {
34 | isNew = false
35 | item.title = action.title
36 | item.content = action.content
37 | item.time = action.time
38 | }
39 | return item
40 | })
41 |
42 | if (isNew) {
43 | return [...state, note({}, action)]
44 | } else {
45 | return _arr
46 | }
47 | default:
48 | return state
49 | }
50 | }
51 |
52 | const noteApp = (state = initialState, action) => {
53 | switch (action.type) {
54 | case ADD_NOTE:
55 | let _notes = notes(state.notes, action)
56 | // 本地存储
57 | setStorage('notes',JSON.stringify(_notes))
58 |
59 | return Object.assign({}, state, {
60 | notes: _notes
61 | })
62 | case SHOW_NOTE:
63 | // 选中时增加active样式
64 | const notesArr = state.notes.map((item) => {
65 | if (item.id === action.id) {
66 | item.isActive = true
67 | } else {
68 | item.isActive = false
69 | }
70 | return item
71 | })
72 | let _cnote = state.notes.filter(item => item.id === action.id)[0]
73 |
74 | // 本地存储
75 | setStorage('notes',JSON.stringify(notesArr))
76 | setStorage('cnote',_cnote)
77 |
78 | return Object.assign({}, state, {
79 | notes: notesArr,
80 | cnote: _cnote
81 | })
82 | // 删除列表
83 | case DELETE_NOTE:
84 | let newnotes = state.notes.filter(item => item.id !== action.id)
85 |
86 | // 本地存储
87 | setStorage('notes',JSON.stringify(newnotes))
88 | setStorage('cnote',{})
89 |
90 | return Object.assign({}, state, {
91 | notes: newnotes,
92 | cnote: {}
93 | })
94 | case SHOW_LAYER:
95 | return Object.assign({}, state, {
96 | isShowLayer: action.isShowLayer
97 | })
98 | case SHOW_EDITER:
99 | return Object.assign({}, state, {
100 | isShowEditer: action.isShowEditer
101 | })
102 | default:
103 | return state
104 | }
105 | }
106 |
107 | export default noteApp
108 |
109 |
--------------------------------------------------------------------------------
/src/components/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | position: absolute;
3 | top: 0;
4 | right: 0;
5 | bottom: 0;
6 | left: 0;
7 | background-color: #fff;
8 | }
9 |
10 | .head-wrap {
11 | width: 100%;
12 | height: 60px;
13 | line-height: 60px;
14 | background-color: #24292e;
15 | box-shadow: 0 1px 5px rgba(0,0,0,0.1);
16 | }
17 |
18 | .logo {
19 | float: left;
20 | margin-left: 15px;
21 | font-weight: normal;
22 | font-size: 16px;
23 | color: #fff;
24 | }
25 |
26 | .head-nav{
27 | overflow: hidden;
28 | float: right;
29 | margin-right: 5px;
30 | list-style: none;
31 | }
32 | .head-nav li{
33 | float: left;
34 | }
35 | .head-nav li a{
36 | display: block;
37 | padding: 0 10px;
38 | height: inherit;
39 | color: #fff;
40 | text-decoration: none;
41 | cursor: pointer;
42 | }
43 | .head-nav li a:hover{
44 | color: #ddd;
45 | }
46 |
47 | main {
48 | position: absolute;
49 | top: 60px;
50 | bottom: 0;
51 | left: 0;
52 | right: 0;
53 | overflow: hidden;
54 | -webkit-transition: top .5s;
55 | transition: top .5s;
56 |
57 | }
58 | main.notop{
59 | top: 0;
60 | }
61 | main.hastop{
62 | top: 60px;
63 | }
64 |
65 | .left-box {
66 | position: relative;
67 | float: left;
68 | width: 20%;
69 | height: 100%;
70 | overflow-y: auto;
71 | background-color: #fafafa;
72 | -webkit-transition: width .5s;
73 | transition:width .5s;
74 | }
75 |
76 | .left-box>h1 {
77 | padding: 10px 0;
78 | text-indent: 15px;
79 | font-size: 16px;
80 | font-weight: normal;
81 | height: 30px;
82 | line-height: 30px;
83 | background-color: #fff;
84 | box-shadow: 0 1px 5px rgba(0,0,0,.15);
85 | }
86 |
87 | .left-box>.add-new {
88 | position: fixed;
89 | display: block;
90 | width: 50px;
91 | height: 50px;
92 | line-height: 50px;
93 | left: 10%;
94 | margin-left: -25px;
95 | bottom: 25px;
96 | text-align: center;
97 | color: #fff;
98 | cursor: pointer;
99 | border-radius: 100%;
100 | background-color: rgba(36, 41, 46, 0.5);
101 | box-shadow: 0px 0px 20px 5px rgba(0,0,0,.1);
102 | z-index: 99;
103 | }
104 | .left-box>.add-new:hover{
105 | background-color: #24292e;
106 | color: #fff;
107 | }
108 |
109 | .right-box {
110 | position: relative;
111 | float: left;
112 | width: 80%;
113 | height: 100%;
114 | overflow-y: auto;
115 | z-index: 100;
116 | background-color: #fff;
117 | -webkit-transition: width .5s;
118 | transition: width .5s;
119 | }
120 |
121 | .list-wrap {
122 | list-style: none;
123 | }
124 |
125 | .list-wrap li {
126 | padding: 5px 15px;
127 | border-bottom: 1px solid #f1f1f1;
128 | cursor: pointer;
129 | }
130 | .list-wrap li .listbox{
131 | width: 100%;
132 | }
133 | .list-wrap .listbox p{
134 | display: none;
135 | color: #47423f;
136 | }
137 | .list-wrap .listbox span{
138 | display: block;
139 | line-height: 30px;
140 | }
141 | .list-wrap .listbox span i{
142 | margin-right: 5px;
143 | display: inline-block;
144 | vertical-align: baseline;
145 | }
146 | .list-wrap .listtitle{
147 | margin-bottom: 0;
148 | width: 100%;
149 | overflow: hidden;
150 | text-overflow: ellipsis;
151 | white-space: nowrap;
152 | }
153 |
154 | .list-wrap li:hover {
155 | background-color: #fff;
156 | color: #47423f;
157 | }
158 |
159 | .list-wrap li.active {
160 | background-color: #fff;
161 | color: #47423f;
162 | }
163 |
164 | p.tips{
165 | margin: 30px 0;
166 | text-align: center;
167 | color: #47423f;
168 | }
169 |
170 | /* 右边的预览区域样式*/
171 | .preview-top{
172 | height: 50px;
173 | line-height: 50px;
174 | text-align: right;
175 | box-shadow: 0 1px 5px rgba(0,0,0,.15);
176 | }
177 | .preview-top>i{
178 | position: absolute;
179 | left: 10px;
180 | top: 10px;
181 | height: 30px;
182 | width: 30px;
183 | line-height: 30px;
184 | text-align: center;
185 | border-radius: 100%;
186 | }
187 | .preview-top>i:active,.preview-top>i:hover{
188 | background-color: #24292e;
189 | color: #fff;
190 | }
191 |
192 | a.modify-btn,a.delete-btn,.full-btn{
193 | display: inline-block;
194 | padding: 0 7px;
195 | height: 30px;
196 | line-height: 30px;
197 | margin: 10px;
198 | border-radius: 15px;
199 | cursor: pointer;
200 | }
201 | a.delete-btn{
202 | margin-right: 20px;
203 | }
204 |
205 | a.modify-btn:hover,a.modify-btn:active,a.full-btn:hover,a.full-btn:active{
206 | background-color: #24292e;
207 | color: #fff;
208 | }
209 |
210 | a.delete-btn:hover,a.delete-btn:active{
211 | background-color: red;
212 | color: #fff;
213 | }
214 |
215 | .preview-wrap{
216 | padding: 10px;
217 | }
218 |
219 | .preview-wrap h1 {
220 | margin-bottom: 10px;
221 | color: #47423f;
222 | }
223 | .preview-wrap p {
224 | line-height: 1.5;
225 | color: #8e857e;
226 | }
227 |
228 |
229 | /* 悬浮层样式 */
230 |
231 | .layer-wrap {
232 | position: absolute;
233 | top: 0;
234 | right: 0;
235 | bottom: 0;
236 | left: 0;
237 | padding: 0;
238 | }
239 |
240 | .layer-container {
241 | position: absolute;
242 | top: 20px;
243 | right: 20px;
244 | bottom: 20px;
245 | left: 20px;
246 | padding: 10px;
247 | box-shadow: 0 0 10px 5px rgba(0,0,0,0.1);
248 | background-color: rgb(255, 255, 255);
249 | border-radius: 5px;
250 | z-index: 100;
251 | }
252 | .layer-left,.layer-right{
253 | padding: 5px;
254 | position: relative;
255 | width: 50%;
256 | height: 100%;
257 | box-sizing: border-box;
258 | }
259 | .layer-left{
260 | float: left;
261 | border-right: 1px solid rgba(0,0,0,0.1);
262 | }
263 |
264 | .layer-right{
265 | float: right;
266 | }
267 | .layer-preview{
268 | position: absolute;
269 | top: 46px;
270 | left: 5px;
271 | right: 5px;
272 | bottom: 0;
273 | overflow-y: auto;
274 | }
275 |
276 | .layer-left>h3,.layer-right>h3{
277 | height: 35px;
278 | line-height: 35px;
279 | border-bottom: 1px solid #f4efea;
280 | margin-bottom: 10px;
281 | }
282 | .icon-save,.icon-close{
283 | position: absolute;
284 | top: 0;
285 | display: block;
286 | height: 35px;
287 | width: 35px;
288 | line-height: 35px;
289 | text-align: center;
290 | z-index: 101;
291 | cursor: pointer;
292 | border-radius: 100%;
293 | }
294 | .icon-save{
295 | right: 50px;
296 | }
297 | .icon-close{
298 | right: 10px;
299 | }
300 | .icon-save:hover,.icon-close:hover{
301 | background-color: #24292e;
302 | color: #fff;
303 | }
304 |
305 | .input-box{
306 | margin-bottom: 10px;
307 | height: 50px;
308 | border-bottom: 1px solid rgba(0,0,0,0.1);
309 | }
310 | .input-box input{
311 | display: block;
312 | width: 100%;
313 | height: inherit;
314 | line-height: 50px;
315 | border: none;
316 | outline: none;
317 | font-size: 16px;
318 | }
319 | .textarea-box{
320 | position: absolute;
321 | top: 107px;
322 | left: 5px;
323 | right: 5px;
324 | bottom: 0;
325 | }
326 | .textarea-box textarea{
327 | width: 100%;
328 | height: 100%;
329 | border: none;
330 | resize: none;
331 | outline: none;
332 | font-size: 16px;
333 | line-height: 1.5;
334 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif;
335 | }
336 |
337 | .layer-preview{
338 | line-height: 1.6;
339 | }
340 | .markdown-body>*:first-child{
341 | text-align: center;
342 | }
343 |
344 | /* 滑动动画 */
345 | .left-box.toLeft{
346 | width: 0;
347 | }
348 | .left-box.toRight{
349 | width: 20%;
350 | }
351 | .right-box.toLeft{
352 | width: 100%;
353 | }
354 | .right-box.toRight{
355 | width: 80%;
356 | }
357 |
358 | @media screen and (max-width: 480px) {
359 | .head-wrap{
360 | height: 50px;
361 | line-height: 50px;
362 | }
363 | .head-show{
364 | height: 50px !important;
365 | }
366 | main{
367 | top: 50px;
368 | }
369 | main.hastop{
370 | top: 50px;
371 | }
372 | .left-box{
373 | float: none;
374 | position: absolute;
375 | top: 0;
376 | bottom: 0;
377 | width: 100%;
378 | z-index: 2;
379 | -webkit-transition: left .5s;
380 | transition: left .5s;
381 | }
382 | .left-box h1{
383 | display: none;
384 | }
385 | .left-box>.add-new{
386 | left: 50%;
387 | background-color: #24292e;
388 | }
389 |
390 | .list-wrap li{
391 | border-bottom: none;
392 | margin-bottom: 10px;
393 | box-shadow: 0 1px 5px rgba(0,0,0,.15);
394 | }
395 |
396 | .list-wrap .listbox p{
397 | display: block;
398 | }
399 |
400 | .list-wrap .listtitle{
401 | margin-bottom: 5px;
402 | width: 100%;
403 | font-size: 18px;
404 | }
405 |
406 | .right-box{
407 | float: none;
408 | position: absolute;
409 | bottom: 0;
410 | width: 100%;
411 | left: 100%;
412 | top: -100%;
413 | -webkit-transition: left .5s;
414 | transition: left .5s;
415 | }
416 |
417 | /* 右边预览区域*/
418 | .preview-top{
419 | height: 40px;
420 | line-height: 40px;
421 | }
422 | .preview-top>i{
423 | display: block;
424 | left: 5px;
425 | top: 5px;
426 | }
427 | a.modify-btn,a.delete-btn,.full-btn{
428 | margin: 5px;
429 | }
430 | a.delete-btn{
431 | margin-right: 5px;
432 | }
433 |
434 | .layer-left{
435 | float: none;
436 | width: 100%;
437 | border-right: none;
438 | }
439 | .layer-right{
440 | display: none;
441 | }
442 | .input-box{
443 | height: 40px;
444 | }
445 | .input-box input{
446 | line-height: 40px;
447 | }
448 | .textarea-box{
449 | top: 97px;
450 | }
451 |
452 | /* 滑动动画 */
453 | .left-box.toLeft{
454 | left: -100%;
455 | width: 100%
456 | }
457 | .left-box.toRight{
458 | left: 0;
459 | width: 100%;
460 | }
461 | .right-box.toLeft{
462 | left: 0;
463 | top: 0;
464 | width: 100%;
465 | }
466 | .right-box.toRight{
467 | left: 100%;
468 | width: 100%;
469 | }
470 | }
--------------------------------------------------------------------------------