208 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<script src="../dist/vue.global.js"></script>
 | 
						|
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css/index.css">
 | 
						|
 | 
						|
<div id="app">
 | 
						|
  <section class="todoapp">
 | 
						|
    <header class="header">
 | 
						|
      <h1>todos</h1>
 | 
						|
      <input class="new-todo"
 | 
						|
              autofocus autocomplete="off"
 | 
						|
              placeholder="What needs to be done?"
 | 
						|
              v-model="state.newTodo"
 | 
						|
              @keyup.enter="addTodo">
 | 
						|
    </header>
 | 
						|
    <section class="main" v-if="state.todos.length">
 | 
						|
      <input id="toggle-all" class="toggle-all" type="checkbox" v-model="state.allDone">
 | 
						|
      <label for="toggle-all">Mark all as complete</label>
 | 
						|
      <ul class="todo-list">
 | 
						|
        <li v-for="todo in state.filteredTodos"
 | 
						|
            class="todo"
 | 
						|
            :key="todo.id"
 | 
						|
            :class="{ completed: todo.completed, editing: todo === state.editedTodo }">
 | 
						|
          <div class="view">
 | 
						|
            <input class="toggle" type="checkbox" v-model="todo.completed">
 | 
						|
            <label @dblclick="editTodo(todo)">{{ todo.title }}</label>
 | 
						|
            <button class="destroy" @click="removeTodo(todo)"></button>
 | 
						|
          </div>
 | 
						|
          <input class="edit" type="text"
 | 
						|
                  v-model="todo.title"
 | 
						|
                  v-todo-focus="todo === state.editedTodo"
 | 
						|
                  @blur="doneEdit(todo)"
 | 
						|
                  @keyup.enter="doneEdit(todo)"
 | 
						|
                  @keyup.escape="cancelEdit(todo)"
 | 
						|
          >
 | 
						|
        </li>
 | 
						|
      </ul>
 | 
						|
    </section>
 | 
						|
    <footer class="footer" v-if="state.todos.length">
 | 
						|
        <span class="todo-count">
 | 
						|
          <strong>{{ state.remaining }}</strong>
 | 
						|
          <span>{{ state.remainingText }}</span>
 | 
						|
        </span>
 | 
						|
      <ul class="filters">
 | 
						|
        <li><a href="#/all" :class="{ selected: state.visibility === 'all' }">All</a></li>
 | 
						|
        <li><a href="#/active" :class="{ selected: state.visibility === 'active' }">Active</a></li>
 | 
						|
        <li><a href="#/completed" :class="{ selected: state.visibility === 'completed' }">Completed</a></li>
 | 
						|
      </ul>
 | 
						|
      <button class="clear-completed" @click="removeCompleted" v-if="state.todos.length > remaining">
 | 
						|
        Clear completed
 | 
						|
      </button>
 | 
						|
    </footer>
 | 
						|
  </section>
 | 
						|
</div>
 | 
						|
 | 
						|
<script>
 | 
						|
const { createApp, reactive, computed, watch, onMounted, onUnmounted } = Vue
 | 
						|
 | 
						|
const STORAGE_KEY = 'todos-vuejs-3.x'
 | 
						|
const todoStorage = {
 | 
						|
  fetch () {
 | 
						|
    const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
 | 
						|
    todos.forEach((todo, index) => {
 | 
						|
      todo.id = index
 | 
						|
    })
 | 
						|
    todoStorage.uid = todos.length
 | 
						|
    return todos
 | 
						|
  },
 | 
						|
  save (todos) {
 | 
						|
    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
const filters = {
 | 
						|
  all (todos) {
 | 
						|
    return todos
 | 
						|
  },
 | 
						|
  active (todos) {
 | 
						|
    return todos.filter((todo) => {
 | 
						|
      return !todo.completed
 | 
						|
    })
 | 
						|
  },
 | 
						|
  completed (todos) {
 | 
						|
    return todos.filter(function (todo) {
 | 
						|
      return todo.completed
 | 
						|
    })
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function pluralize (n) {
 | 
						|
  return n === 1 ? 'item' : 'items'
 | 
						|
}
 | 
						|
 | 
						|
const App = {
 | 
						|
  setup () {
 | 
						|
    const state = reactive({
 | 
						|
      todos: todoStorage.fetch(),
 | 
						|
      editedTodo: null,
 | 
						|
      newTodo: '',
 | 
						|
      beforeEditCache: '',
 | 
						|
      visibility: 'all',
 | 
						|
      remaining: computed(() => {
 | 
						|
        return filters.active(state.todos).length
 | 
						|
      }),
 | 
						|
      remainingText: computed(() => {
 | 
						|
        return ` ${pluralize(state.remaining)} left`
 | 
						|
      }),
 | 
						|
      filteredTodos: computed(() => {
 | 
						|
        return filters[state.visibility](state.todos)
 | 
						|
      }),
 | 
						|
      allDone: computed({
 | 
						|
        get: function () {
 | 
						|
          return state.remaining === 0
 | 
						|
        },
 | 
						|
        set: function (value) {
 | 
						|
          state.todos.forEach((todo) => {
 | 
						|
            todo.completed = value
 | 
						|
          })
 | 
						|
        }
 | 
						|
      })
 | 
						|
    })
 | 
						|
 | 
						|
    watch(() => {
 | 
						|
      todoStorage.save(state.todos)
 | 
						|
    })
 | 
						|
 | 
						|
    onMounted(() => {
 | 
						|
      window.addEventListener('hashchange', onHashChange)
 | 
						|
      onHashChange()
 | 
						|
    })
 | 
						|
 | 
						|
    onUnmounted(() => {
 | 
						|
      window.removeEventListener('hashchange', onHashChange)
 | 
						|
    })
 | 
						|
 | 
						|
    function onHashChange () {
 | 
						|
      const visibility = window.location.hash.replace(/#\/?/, '')
 | 
						|
      if (filters[visibility]) {
 | 
						|
        state.visibility = visibility
 | 
						|
      } else {
 | 
						|
        window.location.hash = ''
 | 
						|
        state.visibility = 'all'
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    function addTodo () {
 | 
						|
      const value = state.newTodo && state.newTodo.trim()
 | 
						|
      if (!value) {
 | 
						|
        return
 | 
						|
      }
 | 
						|
      state.todos.push({
 | 
						|
        id: todoStorage.uid++,
 | 
						|
        title: value,
 | 
						|
        completed: false
 | 
						|
      })
 | 
						|
      state.newTodo = ''
 | 
						|
    }
 | 
						|
 | 
						|
    function removeTodo (todo) {
 | 
						|
      state.todos.splice(state.todos.indexOf(todo), 1)
 | 
						|
    }
 | 
						|
 | 
						|
    function editTodo (todo) {
 | 
						|
      state.beforeEditCache = todo.title
 | 
						|
      state.editedTodo = todo
 | 
						|
    }
 | 
						|
 | 
						|
    function doneEdit (todo) {
 | 
						|
      if (!state.editedTodo) {
 | 
						|
        return
 | 
						|
      }
 | 
						|
      state.editedTodo = null
 | 
						|
      todo.title = todo.title.trim()
 | 
						|
      if (!todo.title) {
 | 
						|
        removeTodo(todo)
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    function cancelEdit (todo) {
 | 
						|
      state.editedTodo = null
 | 
						|
      todo.title = state.beforeEditCache
 | 
						|
    }
 | 
						|
 | 
						|
    function removeCompleted () {
 | 
						|
      state.todos = filters.active(state.todos)
 | 
						|
    }
 | 
						|
 | 
						|
    return {
 | 
						|
      state,
 | 
						|
      addTodo,
 | 
						|
      removeTodo,
 | 
						|
      editTodo,
 | 
						|
      doneEdit,
 | 
						|
      cancelEdit,
 | 
						|
      removeCompleted
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  directives: {
 | 
						|
    'todo-focus': (el, { value }) => {
 | 
						|
      if (value) {
 | 
						|
        el.focus()
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
createApp().mount(App, '#app')
 | 
						|
</script>
 |