8
0
Просмотр исходного кода

Merge tag 'v1.0.0-beta.1' into stable

Release: v1.0.0-beta.1.
Piotrek Koszuliński 7 лет назад
Родитель
Сommit
3ea9852df9
63 измененных файлов с 1567 добавлено и 1586 удалено
  1. 1 1
      packages/ckeditor5-list/.eslintrc.js
  2. 15 11
      packages/ckeditor5-list/.travis.yml
  3. 13 0
      packages/ckeditor5-list/CHANGELOG.md
  4. 1 1
      packages/ckeditor5-list/LICENSE.md
  5. 3 1
      packages/ckeditor5-list/README.md
  6. 1 1
      packages/ckeditor5-list/lang/translations/ast.po
  7. 1 1
      packages/ckeditor5-list/lang/translations/da.po
  8. 1 1
      packages/ckeditor5-list/lang/translations/de.po
  9. 1 1
      packages/ckeditor5-list/lang/translations/el.po
  10. 1 1
      packages/ckeditor5-list/lang/translations/en-au.po
  11. 1 1
      packages/ckeditor5-list/lang/translations/en.po
  12. 1 1
      packages/ckeditor5-list/lang/translations/eo.po
  13. 14 0
      packages/ckeditor5-list/lang/translations/es.po
  14. 14 0
      packages/ckeditor5-list/lang/translations/et.po
  15. 14 0
      packages/ckeditor5-list/lang/translations/eu.po
  16. 1 1
      packages/ckeditor5-list/lang/translations/fi.po
  17. 1 1
      packages/ckeditor5-list/lang/translations/fr.po
  18. 1 1
      packages/ckeditor5-list/lang/translations/gl.po
  19. 1 1
      packages/ckeditor5-list/lang/translations/hr.po
  20. 1 1
      packages/ckeditor5-list/lang/translations/hu.po
  21. 1 1
      packages/ckeditor5-list/lang/translations/it.po
  22. 1 1
      packages/ckeditor5-list/lang/translations/ja.po
  23. 1 1
      packages/ckeditor5-list/lang/translations/km.po
  24. 1 1
      packages/ckeditor5-list/lang/translations/kn.po
  25. 1 1
      packages/ckeditor5-list/lang/translations/ko.po
  26. 1 1
      packages/ckeditor5-list/lang/translations/ku.po
  27. 1 1
      packages/ckeditor5-list/lang/translations/nb.po
  28. 14 0
      packages/ckeditor5-list/lang/translations/nl.po
  29. 1 1
      packages/ckeditor5-list/lang/translations/pl.po
  30. 1 1
      packages/ckeditor5-list/lang/translations/pt-br.po
  31. 1 1
      packages/ckeditor5-list/lang/translations/pt.po
  32. 1 1
      packages/ckeditor5-list/lang/translations/ro.po
  33. 1 1
      packages/ckeditor5-list/lang/translations/ru.po
  34. 14 0
      packages/ckeditor5-list/lang/translations/si.po
  35. 1 1
      packages/ckeditor5-list/lang/translations/sk.po
  36. 14 0
      packages/ckeditor5-list/lang/translations/sq.po
  37. 1 1
      packages/ckeditor5-list/lang/translations/sv.po
  38. 1 1
      packages/ckeditor5-list/lang/translations/tr.po
  39. 14 0
      packages/ckeditor5-list/lang/translations/ug.po
  40. 1 1
      packages/ckeditor5-list/lang/translations/uk.po
  41. 1 1
      packages/ckeditor5-list/lang/translations/zh-cn.po
  42. 1 1
      packages/ckeditor5-list/lang/translations/zh.po
  43. 18 18
      packages/ckeditor5-list/package.json
  44. 344 342
      packages/ckeditor5-list/src/converters.js
  45. 7 58
      packages/ckeditor5-list/src/indentcommand.js
  46. 7 117
      packages/ckeditor5-list/src/list.js
  47. 15 23
      packages/ckeditor5-list/src/listcommand.js
  48. 187 0
      packages/ckeditor5-list/src/listediting.js
  49. 0 138
      packages/ckeditor5-list/src/listengine.js
  50. 64 0
      packages/ckeditor5-list/src/listui.js
  51. 30 0
      packages/ckeditor5-list/src/utils.js
  52. 0 44
      packages/ckeditor5-list/src/viewlistitemelement.js
  53. 66 194
      packages/ckeditor5-list/tests/indentcommand.js
  54. 7 306
      packages/ckeditor5-list/tests/list.js
  55. 97 67
      packages/ckeditor5-list/tests/listcommand.js
  56. 394 180
      packages/ckeditor5-list/tests/listediting.js
  57. 87 0
      packages/ckeditor5-list/tests/listui.js
  58. 2 2
      packages/ckeditor5-list/tests/manual/list.js
  59. 4 4
      packages/ckeditor5-list/tests/manual/nestedlists.js
  60. 75 0
      packages/ckeditor5-list/tests/utils.js
  61. 0 47
      packages/ckeditor5-list/tests/viewlistitemelement.js
  62. 1 1
      packages/ckeditor5-list/theme/icons/bulletedlist.svg
  63. 1 1
      packages/ckeditor5-list/theme/icons/numberedlist.svg

+ 1 - 1
packages/ckeditor5-list/.eslintrc.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 

+ 15 - 11
packages/ckeditor5-list/.travis.yml

@@ -1,24 +1,28 @@
 sudo: required
 dist: trusty
 addons:
+  firefox: "latest"
   apt:
     sources:
-      - google-chrome
+    - google-chrome
     packages:
-      - google-chrome-stable
+    - google-chrome-stable
 language: node_js
 node_js:
-  - "6"
+- '6'
 cache:
-  - node_modules
+- node_modules
 before_install:
-  - export DISPLAY=:99.0
-  - sh -e /etc/init.d/xvfb start
+- export DISPLAY=:99.0
+- sh -e /etc/init.d/xvfb start
 install:
-  - npm install @ckeditor/ckeditor5-dev-tests
-  - ckeditor5-dev-tests-install-dependencies
+- npm install @ckeditor/ckeditor5-dev-tests
+- ckeditor5-dev-tests-install-dependencies
 script:
-  - ckeditor5-dev-tests-travis
+- ckeditor5-dev-tests-travis
 after_success:
-  - codeclimate-test-reporter < coverage/lcov.info
-  - ckeditor5-dev-tests-save-revision
+- ckeditor5-dev-tests-save-revision
+env:
+  global:
+  - secure: nhtYqSDpY1g+H6osP85fi2cZRkpl0W+N8/C8Y/ZxgbV2W39gpmdTNtpAE2xSnF6oVRbhzOhd8WzAMKyK7aC/m8SkkPG2AVOTxj1atjPRaSrZSqCbxrucyxDCSyxQURuXFLknfa5jHBrLsTO+j3Tpzsvrj8M0+8WSImulcY+Ea+UyN7SOmvad1RBUR7lZhYYdyviEWt5YtGIHXlrX3ahfulYpknL6r9wAbhwjdizgNuwFpquU5+piAgRvJkoL8No9K0yh0EtltjHF1JC38qmfcxkh2U3GPAQs7wKd0C+siZ8c5ppNZrGYS/08VOGPLdyyc30o17BZj8eG/3TG/FzcK9TdqlmCHYloAU9RiO0j53NZezYJ1N9uIELSchm/LcU7DtDYKsP0i3UgsaXuJNF4HP2Pas8rig2NrDohsZ00G4a3AD4YCzmKAtejphCH/gx6m0KyZy6QpBgOFM2+50RfB8qMIeGp/FNx1BVYr9yv5YiLeRj1ZocHXgPazwYN8bv1QubowNuMhmsBH8ktRvq7WsuOBbwBSpWgAWghoBKhRzGWmHsuVoHBt54rse+dg2pc/cYtkQsAcuMQAggnW8A3CFx1zwydYpKg8QZBwXwGRmWHILJufogbFefA6pjT1JKNun3+8KAm0NEF2q60+PwZRvcqFrqMhWXT5+NUYiIx0CM=
+  - secure: XCJTk13WNQaM23ovdNigh8zldhxx9WfS3kTcEI87JTI174UEIqKiUVDuXwMQOSBKGI7pzfA4noZEFrn5eU1FqRdcu3F4Rmw/ndLxKFSH8TUz8R4UMosRlPu4Sj68gtfR/BVs0ykkQRTFuD0tXOrb+7cVJVHKDg1OnEIq5FCOl0PeUPHD6U2hr+DX4Okt4ZoPRMGAYuNAUgBc3sWuud32IP/9rri+t7tp+e0cIqYsVoJrkzlXNdGxhnVfCd9bjwkpdILZBrVigmp42NDLon+Ven1RRjWSDunFcljHHtpMUfAmen3cir4fo2e5+HthToQT+1tAXM1885jYvl3mPi2s8KMPkLYtdpscyhzhK8i4gqb6J6i6xNK0PPBWYHhM9s/NCz99zeXMMwEVpS/jiHTlCF6bMWOBaEQUxRVRzLIWxqXS0sqBz5nQEPmP+BleDDQ9xRhYsbfvUFODdOTY+VFYBl8oC5f66H4KBYryY8kISg/1B6rUeWauYgXVdixY4WSeDsU51hLuAR5TyGVKvLcUS1KgA3dJw7bMpgZRGl+BetUE2T1WkJa0FhQUBGhXTr8tXevqDfcCtp25NOb2ozjtEswwjkuUiHDrLYKBJxDZ6wqGLe7GdKwCjQzKJy3PuI/tBx3A4ODTgOgPKt5cD3oeczbOWC1MkYtaOBXEFioDHG8=

+ 13 - 0
packages/ckeditor5-list/CHANGELOG.md

@@ -1,6 +1,19 @@
 Changelog
 =========
 
+## [1.0.0-beta.1](https://github.com/ckeditor/ckeditor5-list/compare/v1.0.0-alpha.2...v1.0.0-beta.1) (2018-03-15)
+
+### Features
+
+* Updated icons for compatibility with the refreshed Lark theme. Minor adjustments in toolbar configurations (see [ckeditor/ckeditor5#645](https://github.com/ckeditor/ckeditor5/issues/645)). ([d1fae4b](https://github.com/ckeditor/ckeditor5-list/commit/d1fae4b))
+
+### Other changes
+
+* Aligned feature class naming to the new scheme. ([d677fb6](https://github.com/ckeditor/ckeditor5-list/commit/d677fb6))
+* Removed `ViewListItemElement` class and introduced `createViewListItemElement()` utility method. Closes [#89](https://github.com/ckeditor/ckeditor5-list/issues/89). ([e4ac704](https://github.com/ckeditor/ckeditor5-list/commit/e4ac704))
+* Updated translations. ([762a9ed](https://github.com/ckeditor/ckeditor5-list/commit/762a9ed))
+
+
 ## [1.0.0-alpha.2](https://github.com/ckeditor/ckeditor5-list/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2017-11-14)
 
 ### Bug fixes

+ 1 - 1
packages/ckeditor5-list/LICENSE.md

@@ -2,7 +2,7 @@ Software License Agreement
 ==========================
 
 **CKEditor5 List Feature** – https://github.com/ckeditor/ckeditor5-list <br>
-Copyright (c) 2003-2017, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
+Copyright (c) 2003-2018, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
 
 Licensed under the terms of any of the following licenses at your choice:
 

+ 3 - 1
packages/ckeditor5-list/README.md

@@ -4,7 +4,9 @@ CKEditor 5 list feature
 [![Join the chat at https://gitter.im/ckeditor/ckeditor5](https://badges.gitter.im/ckeditor/ckeditor5.svg)](https://gitter.im/ckeditor/ckeditor5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 [![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-list.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-list)
 [![Build Status](https://travis-ci.org/ckeditor/ckeditor5-list.svg?branch=master)](https://travis-ci.org/ckeditor/ckeditor5-list)
-[![Test Coverage](https://codeclimate.com/github/ckeditor/ckeditor5-list/badges/coverage.svg)](https://codeclimate.com/github/ckeditor/ckeditor5-list/coverage)
+[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)](https://www.browserstack.com/automate/public-build/d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)
+[![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5-list/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5-list?branch=master)
+<br>
 [![Dependency Status](https://david-dm.org/ckeditor/ckeditor5-list/status.svg)](https://david-dm.org/ckeditor/ckeditor5-list)
 [![devDependency Status](https://david-dm.org/ckeditor/ckeditor5-list/dev-status.svg)](https://david-dm.org/ckeditor/ckeditor5-list?type=dev)
 

+ 1 - 1
packages/ckeditor5-list/lang/translations/ast.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: ast\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/da.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: da\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/de.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: de\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/el.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: el\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/en-au.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: en_AU\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/en.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: \n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/eo.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: eo\n"

+ 14 - 0
packages/ckeditor5-list/lang/translations/es.po

@@ -0,0 +1,14 @@
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+msgid ""
+msgstr ""
+"Language: es\n"
+"Language-Team: Spanish (https://www.transifex.com/ckeditor/teams/11143/es/)\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Toolbar button tooltip for the Numbered List feature."
+msgid "Numbered List"
+msgstr "Lista numerada"
+
+msgctxt "Toolbar button tooltip for the Bulleted List feature."
+msgid "Bulleted List"
+msgstr "Lista de puntos"

+ 14 - 0
packages/ckeditor5-list/lang/translations/et.po

@@ -0,0 +1,14 @@
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+msgid ""
+msgstr ""
+"Language: et\n"
+"Language-Team: Estonian (https://www.transifex.com/ckeditor/teams/11143/et/)\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Toolbar button tooltip for the Numbered List feature."
+msgid "Numbered List"
+msgstr "Nummerdatud loetelu"
+
+msgctxt "Toolbar button tooltip for the Bulleted List feature."
+msgid "Bulleted List"
+msgstr "Loetelu"

+ 14 - 0
packages/ckeditor5-list/lang/translations/eu.po

@@ -0,0 +1,14 @@
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+msgid ""
+msgstr ""
+"Language: eu\n"
+"Language-Team: Basque (https://www.transifex.com/ckeditor/teams/11143/eu/)\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Toolbar button tooltip for the Numbered List feature."
+msgid "Numbered List"
+msgstr "Zenbakidun zerrenda"
+
+msgctxt "Toolbar button tooltip for the Bulleted List feature."
+msgid "Bulleted List"
+msgstr "Buletdun zerrenda"

+ 1 - 1
packages/ckeditor5-list/lang/translations/fi.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: fi\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/fr.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: fr\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/gl.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: gl\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/hr.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: hr\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/hu.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: hu\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/it.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: it\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/ja.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: ja\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/km.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: km\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/kn.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: kn\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/ko.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: ko\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/ku.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: ku\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/nb.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: nb\n"

+ 14 - 0
packages/ckeditor5-list/lang/translations/nl.po

@@ -0,0 +1,14 @@
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+msgid ""
+msgstr ""
+"Language: nl\n"
+"Language-Team: Dutch (https://www.transifex.com/ckeditor/teams/11143/nl/)\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Toolbar button tooltip for the Numbered List feature."
+msgid "Numbered List"
+msgstr "Genummerde lijst"
+
+msgctxt "Toolbar button tooltip for the Bulleted List feature."
+msgid "Bulleted List"
+msgstr "Ongenummerde lijst"

+ 1 - 1
packages/ckeditor5-list/lang/translations/pl.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: pl\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/pt-br.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: pt_BR\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/pt.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: pt\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/ro.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: ro\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/ru.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: ru\n"

+ 14 - 0
packages/ckeditor5-list/lang/translations/si.po

@@ -0,0 +1,14 @@
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+msgid ""
+msgstr ""
+"Language: si_LK\n"
+"Language-Team: Sinhala (Sri Lanka) (https://www.transifex.com/ckeditor/teams/11143/si_LK/)\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Toolbar button tooltip for the Numbered List feature."
+msgid "Numbered List"
+msgstr "අංකිත ලැයිස්තුව"
+
+msgctxt "Toolbar button tooltip for the Bulleted List feature."
+msgid "Bulleted List"
+msgstr "බුලටිත ලැයිස්තුව"

+ 1 - 1
packages/ckeditor5-list/lang/translations/sk.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: sk\n"

+ 14 - 0
packages/ckeditor5-list/lang/translations/sq.po

@@ -0,0 +1,14 @@
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+msgid ""
+msgstr ""
+"Language: sq\n"
+"Language-Team: Albanian (https://www.transifex.com/ckeditor/teams/11143/sq/)\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Toolbar button tooltip for the Numbered List feature."
+msgid "Numbered List"
+msgstr "Listë me Numra"
+
+msgctxt "Toolbar button tooltip for the Bulleted List feature."
+msgid "Bulleted List"
+msgstr "Listë me Pika"

+ 1 - 1
packages/ckeditor5-list/lang/translations/sv.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: sv\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/tr.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: tr\n"

+ 14 - 0
packages/ckeditor5-list/lang/translations/ug.po

@@ -0,0 +1,14 @@
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+msgid ""
+msgstr ""
+"Language: ug\n"
+"Language-Team: Uighur (https://www.transifex.com/ckeditor/teams/11143/ug/)\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Toolbar button tooltip for the Numbered List feature."
+msgid "Numbered List"
+msgstr "نومۇر جەدىۋېلى"
+
+msgctxt "Toolbar button tooltip for the Bulleted List feature."
+msgid "Bulleted List"
+msgstr "بەلگە جەدىۋېلى"

+ 1 - 1
packages/ckeditor5-list/lang/translations/uk.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: uk\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/zh-cn.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: zh_CN\n"

+ 1 - 1
packages/ckeditor5-list/lang/translations/zh.po

@@ -1,4 +1,4 @@
-# Copyright (c) Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 msgid ""
 msgstr ""
 "Language: zh_TW\n"

+ 18 - 18
packages/ckeditor5-list/package.json

@@ -1,32 +1,32 @@
 {
   "name": "@ckeditor/ckeditor5-list",
-  "version": "1.0.0-alpha.2",
+  "version": "1.0.0-beta.1",
   "description": "Ordered and unordered lists feature to CKEditor 5.",
   "keywords": [
     "ckeditor5",
     "ckeditor5-feature"
   ],
   "dependencies": {
-    "@ckeditor/ckeditor5-engine": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-paragraph": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-core": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-ui": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-utils": "^1.0.0-alpha.2"
+    "@ckeditor/ckeditor5-engine": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-paragraph": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-core": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-ui": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-utils": "^1.0.0-beta.1"
   },
   "devDependencies": {
-    "@ckeditor/ckeditor5-basic-styles": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-block-quote": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-clipboard": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-editor-classic": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-enter": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-heading": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-link": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-typing": "^1.0.0-alpha.2",
-    "@ckeditor/ckeditor5-undo": "^1.0.0-alpha.2",
-    "eslint": "^4.8.0",
-    "eslint-config-ckeditor5": "^1.0.6",
+    "@ckeditor/ckeditor5-basic-styles": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-block-quote": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-clipboard": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-editor-classic": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-enter": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-heading": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-link": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-typing": "^1.0.0-beta.1",
+    "@ckeditor/ckeditor5-undo": "^1.0.0-beta.1",
+    "eslint": "^4.15.0",
+    "eslint-config-ckeditor5": "^1.0.7",
     "husky": "^0.14.3",
-    "lint-staged": "^4.2.3"
+    "lint-staged": "^6.0.0"
   },
   "engines": {
     "node": ">=6.0.0",

Разница между файлами не показана из-за своего большого размера
+ 344 - 342
packages/ckeditor5-list/src/converters.js


+ 7 - 58
packages/ckeditor5-list/src/indentcommand.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -49,11 +49,11 @@ export default class IndentCommand extends Command {
 	 * @fires execute
 	 */
 	execute() {
-		const doc = this.editor.document;
-		const batch = doc.batch();
+		const model = this.editor.model;
+		const doc = model.document;
 		let itemsToChange = Array.from( doc.selection.getSelectedBlocks() );
 
-		doc.enqueueChanges( () => {
+		model.change( writer => {
 			const lastItem = itemsToChange[ itemsToChange.length - 1 ];
 
 			// Indenting a list item should also indent all the items that are already sub-items of indented item.
@@ -83,23 +83,13 @@ export default class IndentCommand extends Command {
 					// To keep the model as correct as possible, first rename listItem, then remove attributes,
 					// as listItem without attributes is very incorrect and will cause problems in converters.
 					// No need to remove attributes, will be removed by post fixer.
-					batch.rename( item, 'paragraph' );
+					writer.rename( item, 'paragraph' );
 				}
 				// If indent is >= 0, change the attribute value.
 				else {
-					batch.setAttribute( item, 'indent', indent );
+					writer.setAttribute( 'indent', indent, item );
 				}
 			}
-
-			// Check whether some of changed list items' type should not be fixed.
-			// But first, reverse `itemsToChange` again -- we always want to perform those fixes starting from first item (source-wise).
-			if ( this._indentBy < 0 ) {
-				itemsToChange = itemsToChange.reverse();
-			}
-
-			for ( const item of itemsToChange ) {
-				_fixType( item, batch );
-			}
 		} );
 	}
 
@@ -111,7 +101,7 @@ export default class IndentCommand extends Command {
 	 */
 	_checkEnabled() {
 		// Check whether any of position's ancestor is a list item.
-		const listItem = first( this.editor.document.selection.getSelectedBlocks() );
+		const listItem = first( this.editor.model.document.selection.getSelectedBlocks() );
 
 		// If selection is not in a list item, the command is disabled.
 		if ( !listItem || !listItem.is( 'listItem' ) ) {
@@ -146,44 +136,3 @@ export default class IndentCommand extends Command {
 		return true;
 	}
 }
-
-// Fixes type of `item` element after it was indented/outdented. Looks for a sibling of `item` that has the same
-// indent and sets `item`'s type to the same as that sibling.
-function _fixType( item, batch ) {
-	// Find a preceding sibling of `item` that is a list item of the same list as `item`.
-	const prev = _seekListItem( item, false );
-
-	// If found, fix type.
-	if ( prev ) {
-		batch.setAttribute( item, 'type', prev.getAttribute( 'type' ) );
-
-		return;
-	}
-
-	// If not found, find a following sibling of `item` that is a list item of the same list as `item`.
-	const next = _seekListItem( item, true );
-
-	// If found, fix type.
-	if ( next ) {
-		batch.setAttribute( item, 'type', next.getAttribute( 'type' ) );
-	}
-}
-
-// Seeks for a list item that has same indent as given `item`. May look through next siblings (`seekForward = true`) or
-// previous siblings (`seekForward = false`). Returns found list item or `null` if item has not been found.
-function _seekListItem( item, seekForward ) {
-	let result = item[ seekForward ? 'nextSibling' : 'previousSibling' ];
-
-	// Look for the previous/next sibling that has same indent and is before a list item element with lower indent.
-	// If elements are split by an element with lower indent, they are on different lists.
-	while ( result && result.is( 'listItem' ) && result.getAttribute( 'indent' ) >= item.getAttribute( 'indent' ) ) {
-		if ( result.getAttribute( 'indent' ) == item.getAttribute( 'indent' ) ) {
-			// We found sibling that is on the same list.
-			return result;
-		}
-
-		result = result[ seekForward ? 'nextSibling' : 'previousSibling' ];
-	}
-
-	return null;
-}

+ 7 - 117
packages/ckeditor5-list/src/list.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -7,19 +7,16 @@
  * @module list/list
  */
 
-import ListEngine from './listengine';
-
-import numberedListIcon from '../theme/icons/numberedlist.svg';
-import bulletedListIcon from '../theme/icons/bulletedlist.svg';
+import ListEditing from './listediting';
+import ListUI from './listui';
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
 
 /**
- * The list feature. It introduces the `numberedList` and `bulletedList` buttons that
- * allow to convert paragraphs to and from list items and indent or outdent them.
+ * The list feature.
  *
- * See also {@link module:list/listengine~ListEngine}.
+ * It loads the {@link module:list/listediting~ListEditing list editing feature}
+ * and {@link module:list/listui~ListUI list UI feature}.
  *
  * @extends module:core/plugin~Plugin
  */
@@ -28,7 +25,7 @@ export default class List extends Plugin {
 	 * @inheritDoc
 	 */
 	static get requires() {
-		return [ ListEngine ];
+		return [ ListEditing, ListUI ];
 	}
 
 	/**
@@ -37,111 +34,4 @@ export default class List extends Plugin {
 	static get pluginName() {
 		return 'List';
 	}
-
-	/**
-	 * @inheritDoc
-	 */
-	init() {
-		// Create two buttons and link them with numberedList and bulletedList commands.
-		const t = this.editor.t;
-		this._addButton( 'numberedList', t( 'Numbered List' ), numberedListIcon );
-		this._addButton( 'bulletedList', t( 'Bulleted List' ), bulletedListIcon );
-
-		// Overwrite default Enter key behavior.
-		// If Enter key is pressed with selection collapsed in empty list item, outdent it instead of breaking it.
-		this.listenTo( this.editor.editing.view, 'enter', ( evt, data ) => {
-			const doc = this.editor.document;
-			const positionParent = doc.selection.getLastPosition().parent;
-
-			if ( doc.selection.isCollapsed && positionParent.name == 'listItem' && positionParent.isEmpty ) {
-				this.editor.execute( 'outdentList' );
-
-				data.preventDefault();
-				evt.stop();
-			}
-		} );
-
-		// Overwrite default Backspace key behavior.
-		// If Backspace key is pressed with selection collapsed on first position in first list item, outdent it. #83
-		this.listenTo( this.editor.editing.view, 'delete', ( evt, data ) => {
-			// Check conditions from those that require less computations like those immediately available.
-			if ( data.direction !== 'backward' ) {
-				return;
-			}
-
-			const selection = this.editor.document.selection;
-
-			if ( !selection.isCollapsed ) {
-				return;
-			}
-
-			const firstPosition = selection.getFirstPosition();
-
-			if ( !firstPosition.isAtStart ) {
-				return;
-			}
-
-			const positionParent = firstPosition.parent;
-
-			if ( positionParent.name !== 'listItem' ) {
-				return;
-			}
-
-			const previousIsAListItem = positionParent.previousSibling && positionParent.previousSibling.name === 'listItem';
-
-			if ( previousIsAListItem ) {
-				return;
-			}
-
-			this.editor.execute( 'outdentList' );
-
-			data.preventDefault();
-			evt.stop();
-		}, { priority: 'high' } );
-
-		const getCommandExecuter = commandName => {
-			return ( data, cancel ) => {
-				const command = this.editor.commands.get( commandName );
-
-				if ( command.isEnabled ) {
-					this.editor.execute( commandName );
-					cancel();
-				}
-			};
-		};
-
-		this.editor.keystrokes.set( 'Tab', getCommandExecuter( 'indentList' ) );
-		this.editor.keystrokes.set( 'Shift+Tab', getCommandExecuter( 'outdentList' ) );
-	}
-
-	/**
-	 * Helper method for initializing a button and linking it with an appropriate command.
-	 *
-	 * @private
-	 * @param {String} commandName The name of the command.
-	 * @param {Object} label The button label.
-	 * @param {String} icon The source of the icon.
-	 */
-	_addButton( commandName, label, icon ) {
-		const editor = this.editor;
-		const command = editor.commands.get( commandName );
-
-		editor.ui.componentFactory.add( commandName, locale => {
-			const buttonView = new ButtonView( locale );
-
-			buttonView.set( {
-				label,
-				icon,
-				tooltip: true
-			} );
-
-			// Bind button model to command.
-			buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
-
-			// Execute command.
-			this.listenTo( buttonView, 'execute', () => editor.execute( commandName ) );
-
-			return buttonView;
-		} );
-	}
 }

+ 15 - 23
packages/ckeditor5-list/src/listcommand.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -8,7 +8,6 @@
  */
 
 import Command from '@ckeditor/ckeditor5-core/src/command';
-import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 import first from '@ckeditor/ckeditor5-utils/src/first';
 
 /**
@@ -55,22 +54,18 @@ export default class ListCommand extends Command {
 	 * Executes the command.
 	 *
 	 * @protected
-	 * @param {Object} [options] Options for the executed command.
-	 * @param {module:engine/model/batch~Batch} [options.batch] A batch to collect all the change steps.
-	 * A new batch will be created if this option is not set.
 	 */
-	execute( options = {} ) {
-		const document = this.editor.document;
+	execute() {
+		const model = this.editor.model;
+		const document = model.document;
 		const blocks = Array.from( document.selection.getSelectedBlocks() )
-			.filter( block => checkCanBecomeListItem( block, document.schema ) );
+			.filter( block => checkCanBecomeListItem( block, model.schema ) );
 
 		// Whether we are turning off some items.
 		const turnOff = this.value === true;
 		// If we are turning off items, we are going to rename them to paragraphs.
 
-		document.enqueueChanges( () => {
-			const batch = options.batch || document.batch();
-
+		model.change( writer => {
 			// If part of a list got turned off, we need to handle (outdent) all of sub-items of the last turned-off item.
 			// To be sure that model is all the time in a good state, we first fix items below turned-off item.
 			if ( turnOff ) {
@@ -154,7 +149,7 @@ export default class ListCommand extends Command {
 				changes = changes.reverse();
 
 				for ( const item of changes ) {
-					batch.setAttribute( item.element, 'indent', item.indent );
+					writer.setAttribute( 'indent', item.indent, item.element );
 				}
 			}
 
@@ -204,15 +199,16 @@ export default class ListCommand extends Command {
 				if ( turnOff && element.name == 'listItem' ) {
 					// We are turning off and the element is a `listItem` - it should be converted to `paragraph`.
 					// List item specific attributes are removed by post fixer.
-					batch.rename( element, 'paragraph' );
+					writer.rename( element, 'paragraph' );
 				} else if ( !turnOff && element.name != 'listItem' ) {
 					// We are turning on and the element is not a `listItem` - it should be converted to `listItem`.
 					// The order of operations is important to keep model in correct state.
-					batch.setAttribute( element, 'type', this.type ).setAttribute( element, 'indent', 0 ).rename( element, 'listItem' );
+					writer.setAttributes( { type: this.type, indent: 0 }, element );
+					writer.rename( element, 'listItem' );
 				} else if ( !turnOff && element.name == 'listItem' && element.getAttribute( 'type' ) != this.type ) {
 					// We are turning on and the element is a `listItem` but has different type - change it's type and
 					// type of it's all siblings that have same indent.
-					batch.setAttribute( element, 'type', this.type );
+					writer.setAttribute( 'type', this.type, element );
 				}
 			}
 		} );
@@ -226,7 +222,7 @@ export default class ListCommand extends Command {
 	 */
 	_getValue() {
 		// Check whether closest `listItem` ancestor of the position has a correct type.
-		const listItem = first( this.editor.document.selection.getSelectedBlocks() );
+		const listItem = first( this.editor.model.document.selection.getSelectedBlocks() );
 
 		return !!listItem && listItem.is( 'listItem' ) && listItem.getAttribute( 'type' ) == this.type;
 	}
@@ -243,8 +239,8 @@ export default class ListCommand extends Command {
 			return true;
 		}
 
-		const selection = this.editor.document.selection;
-		const schema = this.editor.document.schema;
+		const selection = this.editor.model.document.selection;
+		const schema = this.editor.model.schema;
 
 		const firstBlock = first( selection.getSelectedBlocks() );
 
@@ -311,9 +307,5 @@ function _fixType( blocks, isBackward, lowestIndent ) {
 // @param {module:engine/model/schema~Schema} schema The schema of the document.
 // @returns {Boolean}
 function checkCanBecomeListItem( block, schema ) {
-	return schema.check( {
-		name: 'listItem',
-		attributes: [ 'type', 'indent' ],
-		inside: Position.createBefore( block )
-	} );
+	return schema.checkChild( block.parent, 'listItem' ) && !schema.isObject( block );
 }

+ 187 - 0
packages/ckeditor5-list/src/listediting.js

@@ -0,0 +1,187 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module list/listediting
+ */
+
+import ListCommand from './listcommand';
+import IndentCommand from './indentcommand';
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+
+import {
+	cleanList,
+	cleanListItem,
+	modelViewInsertion,
+	modelViewChangeType,
+	modelViewMergeAfter,
+	modelViewRemove,
+	modelViewSplitOnInsert,
+	modelViewChangeIndent,
+	modelChangePostFixer,
+	modelIndentPasteFixer,
+	viewModelConverter,
+	modelToViewPosition,
+	viewToModelPosition
+} from './converters';
+
+/**
+ * The engine of the list feature. It handles creating, editing and removing lists and list items.
+ * It registers the `numberedList`, `bulletedList`, `indentList` and `outdentList` commands.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class ListEditing extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ Paragraph ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+
+		// Schema.
+		// Note: in case `$block` will ever be allowed in `listItem`, keep in mind that this feature
+		// uses `Selection#getSelectedBlocks()` without any additional processing to obtain all selected list items.
+		// If there are blocks allowed inside list item, algorithms using `getSelectedBlocks()` will have to be modified.
+		editor.model.schema.register( 'listItem', {
+			inheritAllFrom: '$block',
+			allowAttributes: [ 'type', 'indent' ]
+		} );
+
+		// Converters.
+		const data = editor.data;
+		const editing = editor.editing;
+
+		editor.model.document.registerPostFixer( writer => modelChangePostFixer( editor.model, writer ) );
+
+		editing.mapper.registerViewToModelLength( 'li', getViewListItemLength );
+		data.mapper.registerViewToModelLength( 'li', getViewListItemLength );
+
+		editing.mapper.on( 'modelToViewPosition', modelToViewPosition );
+		editing.mapper.on( 'viewToModelPosition', viewToModelPosition );
+		data.mapper.on( 'modelToViewPosition', modelToViewPosition );
+
+		editing.downcastDispatcher.on( 'insert', modelViewSplitOnInsert, { priority: 'high' } );
+		editing.downcastDispatcher.on( 'insert:listItem', modelViewInsertion );
+		data.downcastDispatcher.on( 'insert', modelViewSplitOnInsert, { priority: 'high' } );
+		data.downcastDispatcher.on( 'insert:listItem', modelViewInsertion );
+
+		editing.downcastDispatcher.on( 'attribute:type:listItem', modelViewChangeType );
+		data.downcastDispatcher.on( 'attribute:type:listItem', modelViewChangeType );
+		editing.downcastDispatcher.on( 'attribute:indent:listItem', modelViewChangeIndent );
+		data.downcastDispatcher.on( 'attribute:indent:listItem', modelViewChangeIndent );
+
+		editing.downcastDispatcher.on( 'remove:listItem', modelViewRemove );
+		editing.downcastDispatcher.on( 'remove', modelViewMergeAfter, { priority: 'low' } );
+		data.downcastDispatcher.on( 'remove:listItem', modelViewRemove );
+		data.downcastDispatcher.on( 'remove', modelViewMergeAfter, { priority: 'low' } );
+
+		data.upcastDispatcher.on( 'element:ul', cleanList, { priority: 'high' } );
+		data.upcastDispatcher.on( 'element:ol', cleanList, { priority: 'high' } );
+		data.upcastDispatcher.on( 'element:li', cleanListItem, { priority: 'high' } );
+		data.upcastDispatcher.on( 'element:li', viewModelConverter );
+
+		// Fix indentation of pasted items.
+		editor.model.on( 'insertContent', modelIndentPasteFixer, { priority: 'high' } );
+
+		// Register commands for numbered and bulleted list.
+		editor.commands.add( 'numberedList', new ListCommand( editor, 'numbered' ) );
+		editor.commands.add( 'bulletedList', new ListCommand( editor, 'bulleted' ) );
+
+		// Register commands for indenting.
+		editor.commands.add( 'indentList', new IndentCommand( editor, 'forward' ) );
+		editor.commands.add( 'outdentList', new IndentCommand( editor, 'backward' ) );
+
+		const viewDocument = this.editor.editing.view.document;
+
+		// Overwrite default Enter key behavior.
+		// If Enter key is pressed with selection collapsed in empty list item, outdent it instead of breaking it.
+		this.listenTo( viewDocument, 'enter', ( evt, data ) => {
+			const doc = this.editor.model.document;
+			const positionParent = doc.selection.getLastPosition().parent;
+
+			if ( doc.selection.isCollapsed && positionParent.name == 'listItem' && positionParent.isEmpty ) {
+				this.editor.execute( 'outdentList' );
+
+				data.preventDefault();
+				evt.stop();
+			}
+		} );
+
+		// Overwrite default Backspace key behavior.
+		// If Backspace key is pressed with selection collapsed on first position in first list item, outdent it. #83
+		this.listenTo( viewDocument, 'delete', ( evt, data ) => {
+			// Check conditions from those that require less computations like those immediately available.
+			if ( data.direction !== 'backward' ) {
+				return;
+			}
+
+			const selection = this.editor.model.document.selection;
+
+			if ( !selection.isCollapsed ) {
+				return;
+			}
+
+			const firstPosition = selection.getFirstPosition();
+
+			if ( !firstPosition.isAtStart ) {
+				return;
+			}
+
+			const positionParent = firstPosition.parent;
+
+			if ( positionParent.name !== 'listItem' ) {
+				return;
+			}
+
+			const previousIsAListItem = positionParent.previousSibling && positionParent.previousSibling.name === 'listItem';
+
+			if ( previousIsAListItem ) {
+				return;
+			}
+
+			this.editor.execute( 'outdentList' );
+
+			data.preventDefault();
+			evt.stop();
+		}, { priority: 'high' } );
+
+		const getCommandExecuter = commandName => {
+			return ( data, cancel ) => {
+				const command = this.editor.commands.get( commandName );
+
+				if ( command.isEnabled ) {
+					this.editor.execute( commandName );
+					cancel();
+				}
+			};
+		};
+
+		this.editor.keystrokes.set( 'Tab', getCommandExecuter( 'indentList' ) );
+		this.editor.keystrokes.set( 'Shift+Tab', getCommandExecuter( 'outdentList' ) );
+	}
+}
+
+function getViewListItemLength( element ) {
+	let length = 1;
+
+	for ( const child of element.getChildren() ) {
+		if ( child.name == 'ul' || child.name == 'ol' ) {
+			for ( const item of child.getChildren() ) {
+				length += getViewListItemLength( item );
+			}
+		}
+	}
+
+	return length;
+}

+ 0 - 138
packages/ckeditor5-list/src/listengine.js

@@ -1,138 +0,0 @@
-/**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/**
- * @module list/listengine
- */
-
-import ListCommand from './listcommand';
-import IndentCommand from './indentcommand';
-
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
-
-import {
-	cleanList,
-	cleanListItem,
-	modelViewInsertion,
-	modelViewChangeType,
-	modelViewMergeAfter,
-	modelViewRemove,
-	modelViewSplitOnInsert,
-	modelViewChangeIndent,
-	modelChangePostFixer,
-	modelIndentPasteFixer,
-	viewModelConverter,
-	modelToViewPosition,
-	viewToModelPosition
-} from './converters';
-
-/**
- * The engine of the list feature. It handles creating, editing and removing lists and list items.
- * It registers the `numberedList`, `bulletedList`, `indentList` and `outdentList` commands.
- *
- * @extends module:core/plugin~Plugin
- */
-export default class ListEngine extends Plugin {
-	/**
-	 * @inheritDoc
-	 */
-	static get requires() {
-		return [ Paragraph ];
-	}
-
-	/**
-	 * @inheritDoc
-	 */
-	init() {
-		const editor = this.editor;
-
-		// Schema.
-		// Note: in case `$block` will be ever allowed in `listItem`, keep in mind that this feature
-		// uses `Selection#getSelectedBlocks()` without any additional processing to obtain all selected list items.
-		// If there are blocks allowed inside list item, algorithms using `getSelectedBlocks()` will have to be modified.
-		const schema = editor.document.schema;
-		schema.registerItem( 'listItem', '$block' );
-		schema.allow( {
-			name: 'listItem',
-			inside: '$root',
-			attributes: [ 'type', 'indent' ]
-		} );
-		schema.requireAttributes( 'listItem', [ 'type', 'indent' ] );
-
-		// Converters.
-		const data = editor.data;
-		const editing = editor.editing;
-
-		this.editor.document.on( 'change', modelChangePostFixer( this.editor.document ), { priority: 'high' } );
-
-		// Unbind all moved model elements before conversion happens. This is important for converters.
-		// TODO: fix this when changes are converted on `changesDone`.
-		this.editor.document.on( 'change', ( evt, type, changes ) => {
-			if ( type == 'move' ) {
-				for ( const item of changes.range.getItems() ) {
-					if ( item.is( 'listItem' ) ) {
-						editing.mapper.unbindModelElement( item );
-					}
-				}
-			}
-		}, { priority: 'high' } );
-
-		editing.mapper.registerViewToModelLength( 'li', getViewListItemLength );
-		data.mapper.registerViewToModelLength( 'li', getViewListItemLength );
-
-		editing.mapper.on( 'modelToViewPosition', modelToViewPosition );
-		editing.mapper.on( 'viewToModelPosition', viewToModelPosition );
-		data.mapper.on( 'modelToViewPosition', modelToViewPosition );
-
-		editing.modelToView.on( 'insert', modelViewSplitOnInsert, { priority: 'high' } );
-		editing.modelToView.on( 'insert:listItem', modelViewInsertion );
-		data.modelToView.on( 'insert', modelViewSplitOnInsert, { priority: 'high' } );
-		data.modelToView.on( 'insert:listItem', modelViewInsertion );
-
-		// Only change converter is needed. List item's type attribute is required, so it's adding is handled when
-		// list item is added and you cannot remove it.
-		editing.modelToView.on( 'changeAttribute:type:listItem', modelViewChangeType );
-		data.modelToView.on( 'changeAttribute:type:listItem', modelViewChangeType );
-
-		editing.modelToView.on( 'remove:listItem', modelViewRemove );
-		editing.modelToView.on( 'remove', modelViewMergeAfter, { priority: 'low' } );
-		data.modelToView.on( 'remove:listItem', modelViewRemove );
-		data.modelToView.on( 'remove', modelViewMergeAfter, { priority: 'low' } );
-
-		editing.modelToView.on( 'changeAttribute:indent:listItem', modelViewChangeIndent );
-		data.modelToView.on( 'changeAttribute:indent:listItem', modelViewChangeIndent );
-
-		data.viewToModel.on( 'element:ul', cleanList, { priority: 'high' } );
-		data.viewToModel.on( 'element:ol', cleanList, { priority: 'high' } );
-		data.viewToModel.on( 'element:li', cleanListItem, { priority: 'high' } );
-		data.viewToModel.on( 'element:li', viewModelConverter );
-
-		// Fix indentation of pasted items.
-		data.on( 'insertContent', modelIndentPasteFixer, { priority: 'high' } );
-
-		// Register commands for numbered and bulleted list.
-		editor.commands.add( 'numberedList', new ListCommand( editor, 'numbered' ) );
-		editor.commands.add( 'bulletedList', new ListCommand( editor, 'bulleted' ) );
-
-		// Register commands for indenting.
-		editor.commands.add( 'indentList', new IndentCommand( editor, 'forward' ) );
-		editor.commands.add( 'outdentList', new IndentCommand( editor, 'backward' ) );
-	}
-}
-
-function getViewListItemLength( element ) {
-	let length = 1;
-
-	for ( const child of element.getChildren() ) {
-		if ( child.name == 'ul' || child.name == 'ol' ) {
-			for ( const item of child.getChildren() ) {
-				length += getViewListItemLength( item );
-			}
-		}
-	}
-
-	return length;
-}

+ 64 - 0
packages/ckeditor5-list/src/listui.js

@@ -0,0 +1,64 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module list/listui
+ */
+
+import numberedListIcon from '../theme/icons/numberedlist.svg';
+import bulletedListIcon from '../theme/icons/bulletedlist.svg';
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+
+/**
+ * The list UI feature. It introduces the `numberedList` and `bulletedList` buttons that
+ * allow to convert paragraphs to and from list items and indent or outdent them.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class ListUI extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		// Create two buttons and link them with numberedList and bulletedList commands.
+		const t = this.editor.t;
+		this._addButton( 'numberedList', t( 'Numbered List' ), numberedListIcon );
+		this._addButton( 'bulletedList', t( 'Bulleted List' ), bulletedListIcon );
+	}
+
+	/**
+	 * Helper method for initializing a button and linking it with an appropriate command.
+	 *
+	 * @private
+	 * @param {String} commandName The name of the command.
+	 * @param {Object} label The button label.
+	 * @param {String} icon The source of the icon.
+	 */
+	_addButton( commandName, label, icon ) {
+		const editor = this.editor;
+
+		editor.ui.componentFactory.add( commandName, locale => {
+			const command = editor.commands.get( commandName );
+
+			const buttonView = new ButtonView( locale );
+
+			buttonView.set( {
+				label,
+				icon,
+				tooltip: true
+			} );
+
+			// Bind button model to command.
+			buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
+
+			// Execute command.
+			this.listenTo( buttonView, 'execute', () => editor.execute( commandName ) );
+
+			return buttonView;
+		} );
+	}
+}

+ 30 - 0
packages/ckeditor5-list/src/utils.js

@@ -0,0 +1,30 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module list/utils
+ */
+
+/**
+ * Creates list item {@link module:engine/view/containerelement~ContainerElement}.
+ *
+ * @param {module:engine/view/writer~Writer} writer The writer instance.
+ * @returns {module:engine/view/containerelement~ContainerElement}
+ */
+export function createViewListItemElement( writer ) {
+	const viewItem = writer.createContainerElement( 'li' );
+	viewItem.getFillerOffset = getFillerOffset;
+
+	return viewItem;
+}
+
+// Implementation of getFillerOffset for view list item element.
+//
+// @returns {Number|null} Block filler offset or `null` if block filler is not needed.
+function getFillerOffset() {
+	const hasOnlyLists = !this.isEmpty && ( this.getChild( 0 ).name == 'ul' || this.getChild( 0 ).name == 'ol' );
+
+	return this.isEmpty || hasOnlyLists ? 0 : null;
+}

+ 0 - 44
packages/ckeditor5-list/src/viewlistitemelement.js

@@ -1,44 +0,0 @@
-/**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/**
- * @module list/viewlistitemelement
- */
-
-import ViewContainerElement from '@ckeditor/ckeditor5-engine/src/view/containerelement';
-
-/**
- * View element class representing a list item (`<li>`). It extends {@link module:engine/view/containerelement~ContainerElement}
- * and overwrites {@link module:list/viewlistitemelement~ViewListItemElement#getFillerOffset evaluating whether filler offset}
- * is needed.
- *
- * @extends module:engine/view/containerelement~ContainerElement
- */
-export default class ViewListItemElement extends ViewContainerElement {
-	/**
-	 * Creates a `<li>` view item.
-	 *
-	 * @param {Object|Iterable} [attrs] A collection of attributes.
-	 * @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children] The list of nodes to be inserted
-	 * into the created element.
-	 */
-	constructor( attrs, children ) {
-		super( 'li', attrs, children );
-
-		/**
-		 * @inheritDoc
-		 */
-		this.getFillerOffset = getFillerOffset;
-	}
-}
-
-// Implementation of getFillerOffset for ViewListItemElements.
-//
-// @returns {Number|null} Block filler offset or `null` if block filler is not needed.
-function getFillerOffset() {
-	const hasOnlyLists = !this.isEmpty && ( this.getChild( 0 ).name == 'ul' || this.getChild( 0 ).name == 'ol' );
-
-	return this.isEmpty || hasOnlyLists ? 0 : null;
-}

+ 66 - 194
packages/ckeditor5-list/tests/indentcommand.js

@@ -1,34 +1,34 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
 import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
-import Document from '@ckeditor/ckeditor5-engine/src/model/document';
+import Model from '@ckeditor/ckeditor5-engine/src/model/model';
 import IndentCommand from '../src/indentcommand';
 import Range from '@ckeditor/ckeditor5-engine/src/model/range';
 import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 describe( 'IndentCommand', () => {
-	let editor, doc, root;
+	let editor, model, doc, root;
 
 	beforeEach( () => {
 		editor = new Editor();
-		editor.document = new Document();
+		editor.model = new Model();
 
-		doc = editor.document;
+		model = editor.model;
+		doc = model.document;
 		root = doc.createRoot();
 
-		doc.schema.registerItem( 'listItem', '$block' );
-		doc.schema.registerItem( 'paragraph', '$block' );
-
-		doc.schema.allow( { name: '$block', inside: '$root' } );
-		doc.schema.allow( { name: 'listItem', attributes: [ 'type', 'indent' ], inside: '$root' } );
-		doc.schema.allow( { name: 'paragraph', inside: '$root' } );
+		model.schema.register( 'listItem', {
+			inheritAllFrom: '$block',
+			allowAttributes: [ 'type', 'indent' ]
+		} );
+		model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 		setData(
-			doc,
+			model,
 			'<listItem indent="0" type="bulleted">a</listItem>' +
 			'<listItem indent="0" type="bulleted">b</listItem>' +
 			'<listItem indent="1" type="bulleted">c</listItem>' +
@@ -52,16 +52,16 @@ describe( 'IndentCommand', () => {
 
 		describe( 'isEnabled', () => {
 			it( 'should be true if selection starts in list item', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 5 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 5 ), 0 );
 				} );
 
 				expect( command.isEnabled ).to.be.true;
 			} );
 
 			it( 'should be false if selection starts in first list item', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 0 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 0 ), 0 );
 				} );
 
 				expect( command.isEnabled ).to.be.false;
@@ -70,7 +70,7 @@ describe( 'IndentCommand', () => {
 			// Reported in PR #53.
 			it( 'should be false if selection starts in first list item #2', () => {
 				setData(
-					doc,
+					model,
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<listItem indent="1" type="bulleted">b</listItem>' +
 					'<listItem indent="0" type="bulleted">c</listItem>' +
@@ -84,7 +84,7 @@ describe( 'IndentCommand', () => {
 			// Reported in PR #53.
 			it( 'should be false if selection starts in first list item #3', () => {
 				setData(
-					doc,
+					model,
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<listItem indent="1" type="bulleted">b</listItem>' +
 					'<listItem indent="0" type="numbered">c</listItem>' +
@@ -97,7 +97,7 @@ describe( 'IndentCommand', () => {
 
 			it( 'should be false if selection starts in first list item of top level list with different type than previous list', () => {
 				setData(
-					doc,
+					model,
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<listItem indent="0" type="numbered">[]b</listItem>'
 				);
@@ -106,8 +106,8 @@ describe( 'IndentCommand', () => {
 			} );
 
 			it( 'should be false if selection starts in a list item that has bigger indent than it\'s previous sibling', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 2 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 2 ), 0 );
 				} );
 
 				expect( command.isEnabled ).to.be.false;
@@ -116,23 +116,37 @@ describe( 'IndentCommand', () => {
 			// Edge case but may happen that some other blocks will also use the indent attribute
 			// and before we fixed it the command was enabled in such a case.
 			it( 'should be false if selection starts in a paragraph with indent attribute', () => {
-				doc.schema.allow( { name: 'paragraph', attributes: [ 'indent' ], inside: '$root' } );
+				model.schema.extend( 'paragraph', { allowAttributes: 'indent' } );
 
-				setData( doc, '<listItem indent="0">a</listItem><paragraph indent="0">b[]</paragraph>' );
+				setData( model, '<listItem indent="0">a</listItem><paragraph indent="0">b[]</paragraph>' );
 
 				expect( command.isEnabled ).to.be.false;
 			} );
 		} );
 
 		describe( 'execute()', () => {
+			it( 'should use parent batch', () => {
+				model.change( writer => {
+					writer.setSelection( root.getChild( 5 ), 0 );
+				} );
+
+				model.change( writer => {
+					expect( writer.batch.deltas.length ).to.equal( 0 );
+
+					command.execute();
+
+					expect( writer.batch.deltas.length ).to.be.above( 0 );
+				} );
+			} );
+
 			it( 'should increment indent attribute by 1', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 5 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 5 ), 0 );
 				} );
 
 				command.execute();
 
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
+				expect( getData( model, { withoutSelection: true } ) ).to.equal(
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<listItem indent="0" type="bulleted">b</listItem>' +
 					'<listItem indent="1" type="bulleted">c</listItem>' +
@@ -144,13 +158,13 @@ describe( 'IndentCommand', () => {
 			} );
 
 			it( 'should increment indent of all sub-items of indented item', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 1 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 1 ), 0 );
 				} );
 
 				command.execute();
 
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
+				expect( getData( model, { withoutSelection: true } ) ).to.equal(
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<listItem indent="1" type="bulleted">b</listItem>' +
 					'<listItem indent="2" type="bulleted">c</listItem>' +
@@ -162,16 +176,16 @@ describe( 'IndentCommand', () => {
 			} );
 
 			it( 'should increment indent of all selected item when multiple items are selected', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setRanges( [ new Range(
+				model.change( writer => {
+					writer.setSelection( new Range(
 						new Position( root.getChild( 1 ), [ 0 ] ),
 						new Position( root.getChild( 3 ), [ 1 ] )
-					) ] );
+					) );
 				} );
 
 				command.execute();
 
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
+				expect( getData( model, { withoutSelection: true } ) ).to.equal(
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<listItem indent="1" type="bulleted">b</listItem>' +
 					'<listItem indent="2" type="bulleted">c</listItem>' +
@@ -181,49 +195,6 @@ describe( 'IndentCommand', () => {
 					'<listItem indent="0" type="bulleted">g</listItem>'
 				);
 			} );
-
-			it( 'should fix list type when item is intended #1', () => {
-				setData(
-					doc,
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="bulleted">b</listItem>' +
-					'<listItem indent="2" type="numbered">c</listItem>' +
-					'<listItem indent="1" type="bulleted">[]d</listItem>'
-				);
-
-				command.execute();
-
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="bulleted">b</listItem>' +
-					'<listItem indent="2" type="numbered">c</listItem>' +
-					'<listItem indent="2" type="numbered">d</listItem>'
-				);
-			} );
-
-			// Not only boundary list items has to be fixed, but also items in the middle of selection.
-			it( 'should fix list type when item is intended #2', () => {
-				setData(
-					doc,
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="numbered">b</listItem>' +
-					'<listItem indent="1" type="numbered">[c</listItem>' +
-					'<listItem indent="0" type="bulleted">d</listItem>' +
-					'<listItem indent="1" type="numbered">e]</listItem>' +
-					'<listItem indent="0" type="bulleted">f</listItem>'
-				);
-
-				command.execute();
-
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="numbered">b</listItem>' +
-					'<listItem indent="2" type="numbered">c</listItem>' +
-					'<listItem indent="1" type="numbered">d</listItem>' +
-					'<listItem indent="2" type="numbered">e</listItem>' +
-					'<listItem indent="0" type="bulleted">f</listItem>'
-				);
-			} );
 		} );
 	} );
 
@@ -240,8 +211,8 @@ describe( 'IndentCommand', () => {
 
 		describe( 'isEnabled', () => {
 			it( 'should be true if selection starts in list item', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 5 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 5 ), 0 );
 				} );
 
 				expect( command.isEnabled ).to.be.true;
@@ -249,8 +220,8 @@ describe( 'IndentCommand', () => {
 
 			it( 'should be true if selection starts in first list item', () => {
 				// This is in contrary to forward indent command.
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 0 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 0 ), 0 );
 				} );
 
 				expect( command.isEnabled ).to.be.true;
@@ -258,8 +229,8 @@ describe( 'IndentCommand', () => {
 
 			it( 'should be true if selection starts in a list item that has bigger indent than it\'s previous sibling', () => {
 				// This is in contrary to forward indent command.
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 2 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 2 ), 0 );
 				} );
 
 				expect( command.isEnabled ).to.be.true;
@@ -268,13 +239,13 @@ describe( 'IndentCommand', () => {
 
 		describe( 'execute()', () => {
 			it( 'should decrement indent attribute by 1 (if it is bigger than 0)', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 5 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 5 ), 0 );
 				} );
 
 				command.execute();
 
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
+				expect( getData( model, { withoutSelection: true } ) ).to.equal(
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<listItem indent="0" type="bulleted">b</listItem>' +
 					'<listItem indent="1" type="bulleted">c</listItem>' +
@@ -286,13 +257,13 @@ describe( 'IndentCommand', () => {
 			} );
 
 			it( 'should rename listItem to paragraph (if indent is equal to 0)', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 0 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 0 ), 0 );
 				} );
 
 				command.execute();
 
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
+				expect( getData( model, { withoutSelection: true } ) ).to.equal(
 					'<paragraph indent="0" type="bulleted">a</paragraph>' +
 					'<listItem indent="0" type="bulleted">b</listItem>' +
 					'<listItem indent="1" type="bulleted">c</listItem>' +
@@ -304,13 +275,13 @@ describe( 'IndentCommand', () => {
 			} );
 
 			it( 'should decrement indent of all sub-items of outdented item', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( root.getChild( 1 ) );
+				model.change( writer => {
+					writer.setSelection( root.getChild( 1 ), 0 );
 				} );
 
 				command.execute();
 
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
+				expect( getData( model, { withoutSelection: true } ) ).to.equal(
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<paragraph indent="0" type="bulleted">b</paragraph>' +
 					'<listItem indent="0" type="bulleted">c</listItem>' +
@@ -322,16 +293,16 @@ describe( 'IndentCommand', () => {
 			} );
 
 			it( 'should outdent all selected item when multiple items are selected', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setRanges( [ new Range(
+				model.change( writer => {
+					writer.setSelection( new Range(
 						new Position( root.getChild( 1 ), [ 0 ] ),
 						new Position( root.getChild( 3 ), [ 1 ] )
-					) ] );
+					) );
 				} );
 
 				command.execute();
 
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
+				expect( getData( model, { withoutSelection: true } ) ).to.equal(
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<paragraph indent="0" type="bulleted">b</paragraph>' +
 					'<listItem indent="0" type="bulleted">c</listItem>' +
@@ -341,105 +312,6 @@ describe( 'IndentCommand', () => {
 					'<listItem indent="0" type="bulleted">g</listItem>'
 				);
 			} );
-
-			it( 'should fix list type when item is outdented #1', () => {
-				setData(
-					doc,
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="bulleted">b</listItem>' +
-					'<listItem indent="2" type="numbered">[]c</listItem>'
-				);
-
-				command.execute();
-
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="bulleted">b</listItem>' +
-					'<listItem indent="1" type="bulleted">c</listItem>'
-				);
-			} );
-
-			// Look at next siblings if, after outdenting, a list item is a first item in it's list (list item "c").
-			it( 'should fix list type when item is outdented #2', () => {
-				setData(
-					doc,
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="numbered">[]b</listItem>' +
-					'<listItem indent="2" type="bulleted">c</listItem>' +
-					'<listItem indent="1" type="numbered">d</listItem>'
-				);
-
-				command.execute();
-
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="0" type="bulleted">b</listItem>' +
-					'<listItem indent="1" type="numbered">c</listItem>' +
-					'<listItem indent="1" type="numbered">d</listItem>'
-				);
-			} );
-
-			// Reported in #53.
-			it( 'should fix list type when item is outdented #3', () => {
-				setData(
-					doc,
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="numbered">[b</listItem>' +
-					'<listItem indent="1" type="numbered">c</listItem>' +
-					'<listItem indent="1" type="numbered">d]</listItem>'
-				);
-
-				command.execute();
-
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="0" type="bulleted">b</listItem>' +
-					'<listItem indent="0" type="bulleted">c</listItem>' +
-					'<listItem indent="0" type="bulleted">d</listItem>'
-				);
-			} );
-
-			it( 'should fix list type when item is outdented to top level (if needed)', () => {
-				setData(
-					doc,
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="numbered">[]b</listItem>' +
-					'<listItem indent="0" type="numbered">c</listItem>'
-				);
-
-				command.execute();
-
-				// Another possible behaviour would be that "b" list item becomes first list item of a top level
-				// numbered list (so it does not change it's type) but it seems less correct from UX standpoint.
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="0" type="bulleted">b</listItem>' +
-					'<listItem indent="0" type="numbered">c</listItem>'
-				);
-			} );
-
-			it( 'should not fix nested list items\' type when not needed', () => {
-				// There was a bug that the nested sub-list changed it's type because for a while, it was on same indent
-				// level as the originally outdented element.
-				setData(
-					doc,
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="numbered">b</listItem>' +
-					'<listItem indent="1" type="numbered">[]c</listItem>' +
-					'<listItem indent="2" type="bulleted">d</listItem>' +
-					'<listItem indent="2" type="bulleted">e</listItem>'
-				);
-
-				command.execute();
-
-				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
-					'<listItem indent="0" type="bulleted">a</listItem>' +
-					'<listItem indent="1" type="numbered">b</listItem>' +
-					'<listItem indent="0" type="bulleted">c</listItem>' +
-					'<listItem indent="1" type="bulleted">d</listItem>' +
-					'<listItem indent="1" type="bulleted">e</listItem>'
-				);
-			} );
 		} );
 	} );
 } );

+ 7 - 306
packages/ckeditor5-list/tests/list.js

@@ -1,317 +1,18 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
-/* globals document */
-
 import List from '../src/list';
-import ListEngine from '../src/listengine';
-
-import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
-import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
-import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
-import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
-import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
-import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import ListEditing from '../src/listediting';
+import ListUI from '../src/listui';
 
 describe( 'List', () => {
-	let editor, doc, bulletedListButton, numberedListButton;
-
-	beforeEach( () => {
-		const editorElement = document.createElement( 'div' );
-		document.body.appendChild( editorElement );
-
-		return ClassicTestEditor.create( editorElement, { plugins: [ Paragraph, BlockQuote, List ] } )
-			.then( newEditor => {
-				editor = newEditor;
-				doc = editor.document;
-
-				bulletedListButton = editor.ui.componentFactory.create( 'bulletedList' );
-				numberedListButton = editor.ui.componentFactory.create( 'numberedList' );
-			} );
-	} );
-
-	afterEach( () => {
-		return editor.destroy();
-	} );
-
-	it( 'should be loaded', () => {
-		expect( editor.plugins.get( List ) ).to.be.instanceOf( List );
-	} );
-
-	it( 'should load ListEngine', () => {
-		expect( editor.plugins.get( ListEngine ) ).to.be.instanceOf( ListEngine );
-	} );
-
-	it( 'should set up keys for bulleted list and numbered list', () => {
-		expect( bulletedListButton ).to.be.instanceOf( ButtonView );
-		expect( numberedListButton ).to.be.instanceOf( ButtonView );
-	} );
-
-	it( 'should execute proper commands when buttons are used', () => {
-		sinon.spy( editor, 'execute' );
-
-		bulletedListButton.fire( 'execute' );
-		expect( editor.execute.calledWithExactly( 'bulletedList' ) );
-
-		numberedListButton.fire( 'execute' );
-		expect( editor.execute.calledWithExactly( 'numberedList' ) );
-	} );
-
-	it( 'should bind bulleted list button model to bulledList command', () => {
-		setData( doc, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-		const command = editor.commands.get( 'bulletedList' );
-
-		expect( bulletedListButton.isOn ).to.be.true;
-		expect( bulletedListButton.isEnabled ).to.be.true;
-
-		command.value = false;
-		expect( bulletedListButton.isOn ).to.be.false;
-
-		command.isEnabled = false;
-		expect( bulletedListButton.isEnabled ).to.be.false;
-	} );
-
-	it( 'should bind numbered list button model to numberedList command', () => {
-		setData( doc, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-		const command = editor.commands.get( 'numberedList' );
-
-		// We are in UL, so numbered list is off.
-		expect( numberedListButton.isOn ).to.be.false;
-		expect( numberedListButton.isEnabled ).to.be.true;
-
-		command.value = true;
-		expect( numberedListButton.isOn ).to.be.true;
-
-		command.isEnabled = false;
-		expect( numberedListButton.isEnabled ).to.be.false;
-	} );
-
-	describe( 'enter key handling callback', () => {
-		it( 'should execute outdentList command on enter key in empty list', () => {
-			const domEvtDataStub = { preventDefault() {} };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<listItem type="bulleted" indent="0">[]</listItem>' );
-
-			editor.editing.view.fire( 'enter', domEvtDataStub );
-
-			expect( editor.execute.calledOnce ).to.be.true;
-			expect( editor.execute.calledWithExactly( 'outdentList' ) );
-		} );
-
-		it( 'should not execute outdentList command on enter key in non-empty list', () => {
-			const domEvtDataStub = { preventDefault() {} };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<listItem type="bulleted" indent="0">foo[]</listItem>' );
-
-			editor.editing.view.fire( 'enter', domEvtDataStub );
-
-			expect( editor.execute.called ).to.be.false;
-		} );
-	} );
-
-	describe( 'delete key handling callback', () => {
-		it( 'should execute outdentList command on backspace key in first item of list', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			expect( editor.execute.calledWithExactly( 'outdentList' ) );
-		} );
-
-		it( 'should execute outdentList command on backspace key in first item of list', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<paragraph>foo</paragraph><listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			expect( editor.execute.calledWithExactly( 'outdentList' ) );
-		} );
-
-		it( 'should not execute outdentList command on delete key in first item of list', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'forward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should not execute outdentList command when selection is not collapsed', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<listItem type="bulleted" indent="0">[fo]o</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should not execute outdentList command if not in list item', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<paragraph>[]foo</paragraph>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should not execute outdentList command if not in first list item', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<listItem type="bulleted" indent="0">foo</listItem><listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should not execute outdentList command when selection is not on first position', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<listItem type="bulleted" indent="0">fo[]o</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should not execute outdentList command when selection is not on first position', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<listItem type="bulleted" indent="0">fo[]o</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should outdent list when previous element is nested in block quote', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<blockQuote><paragraph>x</paragraph></blockQuote><listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			expect( editor.execute.calledWithExactly( 'outdentList' ) );
-		} );
-
-		it( 'should outdent list when list is nested in block quote', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( doc, '<paragraph>x</paragraph><blockQuote><listItem type="bulleted" indent="0">[]foo</listItem></blockQuote>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			expect( editor.execute.calledWithExactly( 'outdentList' ) );
-		} );
+	it( 'should be named', () => {
+		expect( List.pluginName ).to.equal( 'List' );
 	} );
 
-	describe( 'tab key handling callback', () => {
-		let domEvtDataStub;
-
-		beforeEach( () => {
-			domEvtDataStub = {
-				keyCode: getCode( 'Tab' ),
-				preventDefault: sinon.spy(),
-				stopPropagation: sinon.spy()
-			};
-
-			sinon.spy( editor, 'execute' );
-		} );
-
-		afterEach( () => {
-			editor.execute.restore();
-		} );
-
-		it( 'should execute indentList command on tab key', () => {
-			setData(
-				doc,
-				'<listItem type="bulleted" indent="0">foo</listItem>' +
-				'<listItem type="bulleted" indent="0">[]bar</listItem>'
-			);
-
-			editor.editing.view.fire( 'keydown', domEvtDataStub );
-
-			expect( editor.execute.calledOnce ).to.be.true;
-			expect( editor.execute.calledWithExactly( 'indentList' ) ).to.be.true;
-			sinon.assert.calledOnce( domEvtDataStub.preventDefault );
-			sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
-		} );
-
-		it( 'should execute outdentList command on Shift+Tab keystroke', () => {
-			domEvtDataStub.keyCode += getCode( 'Shift' );
-
-			setData(
-				doc,
-				'<listItem type="bulleted" indent="0">foo</listItem>' +
-				'<listItem type="bulleted" indent="1">[]bar</listItem>'
-			);
-
-			editor.editing.view.fire( 'keydown', domEvtDataStub );
-
-			expect( editor.execute.calledOnce ).to.be.true;
-			expect( editor.execute.calledWithExactly( 'outdentList' ) ).to.be.true;
-			sinon.assert.calledOnce( domEvtDataStub.preventDefault );
-			sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
-		} );
-
-		it( 'should not indent if command is disabled', () => {
-			setData( doc, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'keydown', domEvtDataStub );
-
-			expect( editor.execute.called ).to.be.false;
-			sinon.assert.notCalled( domEvtDataStub.preventDefault );
-			sinon.assert.notCalled( domEvtDataStub.stopPropagation );
-		} );
-
-		it( 'should not indent or outdent if alt+tab is pressed', () => {
-			domEvtDataStub.keyCode += getCode( 'alt' );
-
-			setData(
-				doc,
-				'<listItem type="bulleted" indent="0">foo</listItem>' +
-				'<listItem type="bulleted" indent="0">[]bar</listItem>'
-			);
-
-			editor.editing.view.fire( 'keydown', domEvtDataStub );
-
-			expect( editor.execute.called ).to.be.false;
-			sinon.assert.notCalled( domEvtDataStub.preventDefault );
-			sinon.assert.notCalled( domEvtDataStub.stopPropagation );
-		} );
+	it( 'should require ListEditing and ListUI', () => {
+		expect( List.requires ).to.deep.equal( [ ListEditing, ListUI ] );
 	} );
 } );

+ 97 - 67
packages/ckeditor5-list/tests/listcommand.js

@@ -1,38 +1,46 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
 import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
-import Document from '@ckeditor/ckeditor5-engine/src/model/document';
+import Model from '@ckeditor/ckeditor5-engine/src/model/model';
 import ListCommand from '../src/listcommand';
 import Range from '@ckeditor/ckeditor5-engine/src/model/range';
 import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 describe( 'ListCommand', () => {
-	let editor, command, doc, root;
+	let editor, command, model, doc, root;
 
 	beforeEach( () => {
 		editor = new Editor();
-		editor.document = new Document();
+		editor.model = new Model();
 
-		doc = editor.document;
+		model = editor.model;
+		doc = model.document;
 		root = doc.createRoot();
 
 		command = new ListCommand( editor, 'bulleted' );
 
-		doc.schema.registerItem( 'listItem', '$block' );
-		doc.schema.registerItem( 'paragraph', '$block' );
-		doc.schema.registerItem( 'widget', '$block' );
+		model.schema.register( 'listItem', {
+			inheritAllFrom: '$block',
+			allowAttributes: [ 'type', 'indent' ]
+		} );
+		model.schema.register( 'paragraph', {
+			inheritAllFrom: '$block',
+			allowIn: 'widget'
+		} );
+		model.schema.register( 'widget', { inheritAllFrom: '$block' } );
 
-		doc.schema.allow( { name: '$block', inside: '$root' } );
-		doc.schema.allow( { name: 'paragraph', inside: 'widget' } );
-		doc.schema.allow( { name: 'listItem', attributes: [ 'type', 'indent' ], inside: '$root' } );
-		doc.schema.disallow( { name: 'listItem', attributes: [ 'type', 'indent' ], inside: 'widget' } );
+		model.schema.addChildCheck( ( ctx, childDef ) => {
+			if ( ctx.endsWith( 'widget' ) && childDef.name == 'listItem' ) {
+				return false;
+			}
+		} );
 
 		setData(
-			doc,
+			model,
 			'<paragraph>foo</paragraph>' +
 			'<listItem type="bulleted" indent="0">bulleted</listItem>' +
 			'<listItem type="numbered" indent="0">numbered</listItem>' +
@@ -42,7 +50,9 @@ describe( 'ListCommand', () => {
 			'</widget>'
 		);
 
-		doc.selection.setCollapsedAt( doc.getRoot().getChild( 0 ) );
+		model.change( writer => {
+			writer.setSelection( doc.getRoot().getChild( 0 ), 0 );
+		} );
 	} );
 
 	afterEach( () => {
@@ -62,24 +72,24 @@ describe( 'ListCommand', () => {
 
 		describe( 'value', () => {
 			it( 'should be false if first position in selection is not in a list item', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( doc.getRoot().getChild( 3 ) );
+				model.change( writer => {
+					writer.setSelection( doc.getRoot().getChild( 3 ), 0 );
 				} );
 
 				expect( command.value ).to.be.false;
 			} );
 
 			it( 'should be false if first position in selection is in a list item of different type', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( doc.getRoot().getChild( 2 ) );
+				model.change( writer => {
+					writer.setSelection( doc.getRoot().getChild( 2 ), 0 );
 				} );
 
 				expect( command.value ).to.be.false;
 			} );
 
 			it( 'should be true if first position in selection is in a list item of same type', () => {
-				doc.enqueueChanges( () => {
-					doc.selection.setCollapsedAt( doc.getRoot().getChild( 1 ) );
+				model.change( writer => {
+					writer.setSelection( doc.getRoot().getChild( 1 ), 0 );
 				} );
 
 				expect( command.value ).to.be.true;
@@ -88,69 +98,67 @@ describe( 'ListCommand', () => {
 
 		describe( 'isEnabled', () => {
 			it( 'should be true if entire selection is in a list', () => {
-				setData( doc, '<listItem type="bulleted" indent="0">[a]</listItem>' );
+				setData( model, '<listItem type="bulleted" indent="0">[a]</listItem>' );
 				expect( command.isEnabled ).to.be.true;
 			} );
 
 			it( 'should be true if entire selection is in a block which can be turned into a list', () => {
-				setData( doc, '<paragraph>[a]</paragraph>' );
+				setData( model, '<paragraph>[a]</paragraph>' );
 				expect( command.isEnabled ).to.be.true;
 			} );
 
 			it( 'should be true if selection first position is in a block which can be turned into a list', () => {
-				setData( doc, '<paragraph>[a</paragraph><widget>b]</widget>' );
+				setData( model, '<paragraph>[a</paragraph><widget>b]</widget>' );
 				expect( command.isEnabled ).to.be.true;
 			} );
 
 			it( 'should be false if selection first position is in an element which cannot be converted to a list item', () => {
-				setData( doc, '<widget><paragraph>[a</paragraph></widget><paragraph>b]</paragraph>' );
+				setData( model, '<widget><paragraph>[a</paragraph></widget><paragraph>b]</paragraph>' );
 				expect( command.isEnabled ).to.be.false;
 			} );
 
 			it( 'should be false in a root which does not allow blocks at all', () => {
 				doc.createRoot( 'paragraph', 'inlineOnlyRoot' );
-				setData( doc, 'a[]b', { rootName: 'inlineOnlyRoot' } );
+				setData( model, 'a[]b', { rootName: 'inlineOnlyRoot' } );
 				expect( command.isEnabled ).to.be.false;
 			} );
 		} );
 
 		describe( 'execute()', () => {
-			describe( 'custom options', () => {
-				it( 'should use provided batch', () => {
-					const batch = editor.document.batch();
-
-					expect( batch.deltas.length ).to.equal( 0 );
+			it( 'should use parent batch', () => {
+				model.change( writer => {
+					expect( writer.batch.deltas.length ).to.equal( 0 );
 
-					command.execute( { batch } );
+					command.execute();
 
-					expect( batch.deltas.length ).to.be.above( 0 );
+					expect( writer.batch.deltas.length ).to.be.above( 0 );
 				} );
 			} );
 
 			describe( 'collapsed selection', () => {
 				it( 'should rename closest block to listItem and set correct attributes', () => {
-					setData( doc, '<paragraph>fo[]o</paragraph>' );
+					setData( model, '<paragraph>fo[]o</paragraph>' );
 
 					command.execute();
 
-					expect( getData( doc ) ).to.equal( '<listItem indent="0" type="bulleted">fo[]o</listItem>' );
+					expect( getData( model ) ).to.equal( '<listItem indent="0" type="bulleted">fo[]o</listItem>' );
 				} );
 
 				it( 'should rename closest listItem to paragraph', () => {
-					setData( doc, '<listItem indent="0" type="bulleted">fo[]o</listItem>' );
+					setData( model, '<listItem indent="0" type="bulleted">fo[]o</listItem>' );
 
 					command.execute();
 
 					// Attributes will be removed by post fixer.
-					expect( getData( doc ) ).to.equal( '<paragraph indent="0" type="bulleted">fo[]o</paragraph>' );
+					expect( getData( model ) ).to.equal( '<paragraph indent="0" type="bulleted">fo[]o</paragraph>' );
 				} );
 
 				it( 'should change closest listItem\' type', () => {
-					setData( doc, '<listItem indent="0" type="numbered">fo[]o</listItem>' );
+					setData( model, '<listItem indent="0" type="numbered">fo[]o</listItem>' );
 
 					command.execute();
 
-					expect( getData( doc ) ).to.equal( '<listItem indent="0" type="bulleted">fo[]o</listItem>' );
+					expect( getData( model ) ).to.equal( '<listItem indent="0" type="bulleted">fo[]o</listItem>' );
 				} );
 
 				it( 'should handle outdenting sub-items when list item is turned off', () => {
@@ -193,7 +201,7 @@ describe( 'ListCommand', () => {
 					/* eslint-enable max-len */
 
 					setData(
-						doc,
+						model,
 						'<listItem indent="0" type="bulleted">---</listItem>' +
 						'<listItem indent="1" type="bulleted">---</listItem>' +
 						'<listItem indent="2" type="bulleted">[]---</listItem>' +
@@ -228,14 +236,14 @@ describe( 'ListCommand', () => {
 						'<listItem indent="1" type="bulleted">---</listItem>' +
 						'<listItem indent="2" type="bulleted">---</listItem>';
 
-					expect( getData( doc ) ).to.equal( expectedData );
+					expect( getData( model ) ).to.equal( expectedData );
 				} );
 			} );
 
 			describe( 'non-collapsed selection', () => {
 				beforeEach( () => {
 					setData(
-						doc,
+						model,
 						'<listItem indent="0" type="bulleted">---</listItem>' +
 						'<listItem indent="0" type="bulleted">---</listItem>' +
 						'<paragraph>---</paragraph>' +
@@ -248,16 +256,15 @@ describe( 'ListCommand', () => {
 				} );
 
 				// https://github.com/ckeditor/ckeditor5-list/issues/62
-				it( 'should not rename blocks which cannot become listItems', () => {
-					doc.schema.registerItem( 'restricted' );
-					doc.schema.allow( { name: 'restricted', inside: '$root' } );
-					doc.schema.disallow( { name: 'paragraph', inside: 'restricted' } );
+				it( 'should not rename blocks which cannot become listItems (list item is not allowed in their parent)', () => {
+					model.schema.register( 'restricted' );
+					model.schema.extend( 'restricted', { allowIn: '$root' } );
 
-					doc.schema.registerItem( 'fooBlock', '$block' );
-					doc.schema.allow( { name: 'fooBlock', inside: 'restricted' } );
+					model.schema.register( 'fooBlock', { inheritAllFrom: '$block' } );
+					model.schema.extend( 'fooBlock', { allowIn: 'restricted' } );
 
 					setData(
-						doc,
+						model,
 						'<paragraph>a[bc</paragraph>' +
 						'<restricted><fooBlock></fooBlock></restricted>' +
 						'<paragraph>de]f</paragraph>'
@@ -265,21 +272,44 @@ describe( 'ListCommand', () => {
 
 					command.execute();
 
-					expect( getData( doc ) ).to.equal(
+					expect( getData( model ) ).to.equal(
 						'<listItem indent="0" type="bulleted">a[bc</listItem>' +
 						'<restricted><fooBlock></fooBlock></restricted>' +
 						'<listItem indent="0" type="bulleted">de]f</listItem>'
 					);
 				} );
 
+				it( 'should not rename blocks which cannot become listItems (block is an object)', () => {
+					model.schema.register( 'image', {
+						isBlock: true,
+						isObject: true,
+						allowIn: '$root'
+					} );
+
+					setData(
+						model,
+						'<paragraph>a[bc</paragraph>' +
+						'<image></image>' +
+						'<paragraph>de]f</paragraph>'
+					);
+
+					command.execute();
+
+					expect( getData( model ) ).to.equal(
+						'<listItem indent="0" type="bulleted">a[bc</listItem>' +
+						'<image></image>' +
+						'<listItem indent="0" type="bulleted">de]f</listItem>'
+					);
+				} );
+
 				it( 'should rename closest block to listItem and set correct attributes', () => {
 					// From first paragraph to second paragraph.
 					// Command value=false, we are turning on list items.
-					doc.enqueueChanges( () => {
-						doc.selection.setRanges( [ new Range(
+					model.change( writer => {
+						writer.setSelection( new Range(
 							Position.createAt( root.getChild( 2 ) ),
 							Position.createAt( root.getChild( 3 ), 'end' )
-						) ] );
+						) );
 					} );
 
 					command.execute();
@@ -294,17 +324,17 @@ describe( 'ListCommand', () => {
 						'<listItem indent="1" type="bulleted">---</listItem>' +
 						'<listItem indent="2" type="bulleted">---</listItem>';
 
-					expect( getData( doc ) ).to.equal( expectedData );
+					expect( getData( model ) ).to.equal( expectedData );
 				} );
 
 				it( 'should rename closest listItem to paragraph', () => {
 					// From second bullet list item to first numbered list item.
 					// Command value=true, we are turning off list items.
-					doc.enqueueChanges( () => {
-						doc.selection.setRanges( [ new Range(
+					model.change( writer => {
+						writer.setSelection( new Range(
 							Position.createAt( root.getChild( 1 ) ),
 							Position.createAt( root.getChild( 4 ), 'end' )
-						) ] );
+						) );
 					} );
 
 					// Convert paragraphs, leave numbered list items.
@@ -320,16 +350,16 @@ describe( 'ListCommand', () => {
 						'<listItem indent="1" type="bulleted">---</listItem>' +
 						'<listItem indent="2" type="bulleted">---</listItem>';
 
-					expect( getData( doc ) ).to.equal( expectedData );
+					expect( getData( model ) ).to.equal( expectedData );
 				} );
 
 				it( 'should change closest listItem\'s type', () => {
 					// From first numbered lsit item to third bulleted list item.
-					doc.enqueueChanges( () => {
-						doc.selection.setRanges( [ new Range(
+					model.change( writer => {
+						writer.setSelection( new Range(
 							Position.createAt( root.getChild( 4 ) ),
 							Position.createAt( root.getChild( 6 ) )
-						) ] );
+						) );
 					} );
 
 					// Convert paragraphs, leave numbered list items.
@@ -345,16 +375,16 @@ describe( 'ListCommand', () => {
 						'<listItem indent="1" type="bulleted">]---</listItem>' +
 						'<listItem indent="2" type="bulleted">---</listItem>';
 
-					expect( getData( doc ) ).to.equal( expectedData );
+					expect( getData( model ) ).to.equal( expectedData );
 				} );
 
 				it( 'should handle outdenting sub-items when list item is turned off', () => {
 					// From first numbered list item to third bulleted list item.
-					doc.enqueueChanges( () => {
-						doc.selection.setRanges( [ new Range(
+					model.change( writer => {
+						writer.setSelection( new Range(
 							Position.createAt( root.getChild( 1 ) ),
 							Position.createAt( root.getChild( 5 ), 'end' )
-						) ] );
+						) );
 					} );
 
 					// Convert paragraphs, leave numbered list items.
@@ -370,13 +400,13 @@ describe( 'ListCommand', () => {
 						'<listItem indent="0" type="bulleted">---</listItem>' +
 						'<listItem indent="1" type="bulleted">---</listItem>';
 
-					expect( getData( doc ) ).to.equal( expectedData );
+					expect( getData( model ) ).to.equal( expectedData );
 				} );
 
 				// Example from docs.
 				it( 'should change type of all items in nested list if one of items changed', () => {
 					setData(
-						doc,
+						model,
 						'<listItem indent="0" type="numbered">---</listItem>' +
 						'<listItem indent="1" type="numbered">---</listItem>' +
 						'<listItem indent="2" type="numbered">---</listItem>' +
@@ -423,7 +453,7 @@ describe( 'ListCommand', () => {
 						'<listItem indent="2" type="numbered">---</listItem>' +
 						'<listItem indent="0" type="numbered">---</listItem>';
 
-					expect( getData( doc ) ).to.equal( expectedData );
+					expect( getData( model ) ).to.equal( expectedData );
 				} );
 			} );
 		} );

Разница между файлами не показана из-за своего большого размера
+ 394 - 180
packages/ckeditor5-list/tests/listediting.js


+ 87 - 0
packages/ckeditor5-list/tests/listui.js

@@ -0,0 +1,87 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals document */
+
+import ListEditing from '../src/listediting';
+import ListUI from '../src/listui';
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+describe( 'ListUI', () => {
+	let editor, model, bulletedListButton, numberedListButton;
+
+	beforeEach( () => {
+		const editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicTestEditor.create( editorElement, { plugins: [ Paragraph, BlockQuote, ListEditing, ListUI ] } )
+			.then( newEditor => {
+				editor = newEditor;
+				model = editor.model;
+
+				bulletedListButton = editor.ui.componentFactory.create( 'bulletedList' );
+				numberedListButton = editor.ui.componentFactory.create( 'numberedList' );
+			} );
+	} );
+
+	afterEach( () => {
+		return editor.destroy();
+	} );
+
+	it( 'should be loaded', () => {
+		expect( editor.plugins.get( ListUI ) ).to.be.instanceOf( ListUI );
+	} );
+
+	it( 'should set up buttons for bulleted list and numbered list', () => {
+		expect( bulletedListButton ).to.be.instanceOf( ButtonView );
+		expect( numberedListButton ).to.be.instanceOf( ButtonView );
+	} );
+
+	it( 'should execute proper commands when buttons are used', () => {
+		sinon.spy( editor, 'execute' );
+
+		bulletedListButton.fire( 'execute' );
+		sinon.assert.calledWithExactly( editor.execute, 'bulletedList' );
+
+		numberedListButton.fire( 'execute' );
+		sinon.assert.calledWithExactly( editor.execute, 'numberedList' );
+	} );
+
+	it( 'should bind bulleted list button model to bulledList command', () => {
+		setData( model, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
+
+		const command = editor.commands.get( 'bulletedList' );
+
+		expect( bulletedListButton.isOn ).to.be.true;
+		expect( bulletedListButton.isEnabled ).to.be.true;
+
+		command.value = false;
+		expect( bulletedListButton.isOn ).to.be.false;
+
+		command.isEnabled = false;
+		expect( bulletedListButton.isEnabled ).to.be.false;
+	} );
+
+	it( 'should bind numbered list button model to numberedList command', () => {
+		setData( model, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
+
+		const command = editor.commands.get( 'numberedList' );
+
+		// We are in UL, so numbered list is off.
+		expect( numberedListButton.isOn ).to.be.false;
+		expect( numberedListButton.isEnabled ).to.be.true;
+
+		command.value = true;
+		expect( numberedListButton.isOn ).to.be.true;
+
+		command.isEnabled = false;
+		expect( numberedListButton.isEnabled ).to.be.false;
+	} );
+} );

+ 2 - 2
packages/ckeditor5-list/tests/manual/list.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -17,7 +17,7 @@ import List from '../../src/list';
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
 		plugins: [ Enter, Typing, Heading, Paragraph, Undo, List, Clipboard ],
-		toolbar: [ 'headings', 'bulletedList', 'numberedList', 'undo', 'redo' ]
+		toolbar: [ 'heading', '|', 'bulletedList', 'numberedList', 'undo', 'redo' ]
 	} )
 	.then( editor => {
 		window.editor = editor;

+ 4 - 4
packages/ckeditor5-list/tests/manual/nestedlists.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -18,12 +18,12 @@ import List from '../../src/list';
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
 		plugins: [ Enter, Typing, Heading, Paragraph, Undo, List, Clipboard, Link ],
-		toolbar: [ 'headings', 'bulletedList', 'numberedList', 'undo', 'redo' ]
+		toolbar: [ 'heading', '|', 'bulletedList', 'numberedList', 'undo', 'redo' ]
 	} )
 	.then( editor => {
 		window.editor = editor;
-		window.modelRoot = editor.document.getRoot();
-		window.viewRoot = editor.editing.view.getRoot();
+		window.modelRoot = editor.model.document.getRoot();
+		window.viewRoot = editor.editing.view.document.getRoot();
 	} )
 	.catch( err => {
 		console.error( err.stack );

+ 75 - 0
packages/ckeditor5-list/tests/utils.js

@@ -0,0 +1,75 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import ViewContainerElement from '@ckeditor/ckeditor5-engine/src/view/containerelement';
+import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
+import ViewWriter from '@ckeditor/ckeditor5-engine/src/view/writer';
+import { createViewListItemElement } from '../src/utils';
+
+describe( 'utils', () => {
+	let writer;
+
+	beforeEach( () => {
+		writer = new ViewWriter( {} );
+	} );
+
+	describe( 'createViewListItemElement()', () => {
+		it( 'should create ViewContainerElement', () => {
+			const item = createViewListItemElement( writer );
+
+			expect( item ).to.be.instanceof( ViewContainerElement );
+		} );
+
+		it( 'should have li name', () => {
+			const item = createViewListItemElement( writer );
+
+			expect( item.name ).to.equal( 'li' );
+		} );
+
+		describe( 'getFillerOffset', () => {
+			it( 'should return 0 if item is empty', () => {
+				const item = createViewListItemElement( writer );
+
+				expect( item.getFillerOffset() ).to.equal( 0 );
+			} );
+
+			it( 'should return 0 if item has only lists as children', () => {
+				const innerListItem1 = createViewListItemElement( writer );
+
+				writer.insert(
+					ViewPosition.createAt( innerListItem1 ),
+					writer.createText( 'foo' )
+				);
+
+				const innerListItem2 = createViewListItemElement( writer );
+
+				writer.insert(
+					ViewPosition.createAt( innerListItem2 ),
+					writer.createText( 'bar' )
+				);
+
+				const innerList = writer.createContainerElement( 'ul' );
+				writer.insert( ViewPosition.createAt( innerList ), innerListItem1 );
+				writer.insert( ViewPosition.createAt( innerList ), innerListItem2 );
+
+				const outerListItem = createViewListItemElement( writer );
+				writer.insert( ViewPosition.createAt( outerListItem ), innerList );
+
+				expect( outerListItem.getFillerOffset() ).to.equal( 0 );
+			} );
+
+			it( 'should return null if item has non-list contents', () => {
+				const item = createViewListItemElement( writer );
+
+				writer.insert(
+					ViewPosition.createAt( item ),
+					writer.createText( 'foo' )
+				);
+
+				expect( item.getFillerOffset() ).to.be.null;
+			} );
+		} );
+	} );
+} );

+ 0 - 47
packages/ckeditor5-list/tests/viewlistitemelement.js

@@ -1,47 +0,0 @@
-/**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import ViewListItemElement from '../src/viewlistitemelement';
-import ViewContainerElement from '@ckeditor/ckeditor5-engine/src/view/containerelement';
-import ViewText from '@ckeditor/ckeditor5-engine/src/view/text';
-
-describe( 'ViewListItemElement', () => {
-	it( 'should extend ViewContainerElement', () => {
-		const item = new ViewListItemElement();
-
-		expect( item ).to.be.instanceof( ViewContainerElement );
-	} );
-
-	it( 'should have li name', () => {
-		const item = new ViewListItemElement();
-
-		expect( item.name ).to.equal( 'li' );
-	} );
-
-	describe( 'getFillerOffset', () => {
-		it( 'should return 0 if item is empty', () => {
-			const item = new ViewListItemElement();
-
-			expect( item.getFillerOffset() ).to.equal( 0 );
-		} );
-
-		it( 'should return 0 if item has only lists as children', () => {
-			const item = new ViewListItemElement( null, [
-				new ViewContainerElement( 'ul', null, [
-					new ViewListItemElement( null, new ViewText( 'foo' ) ),
-					new ViewListItemElement( null, new ViewText( 'bar' ) )
-				] )
-			] );
-
-			expect( item.getFillerOffset() ).to.equal( 0 );
-		} );
-
-		it( 'should return null if item has non-list contents', () => {
-			const item = new ViewListItemElement( null, new ViewText( 'foo' ) );
-
-			expect( item.getFillerOffset() ).to.be.null;
-		} );
-	} );
-} );

+ 1 - 1
packages/ckeditor5-list/theme/icons/bulletedlist.svg

@@ -1 +1 @@
-<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M6 16v1h10v-1H6zM6 4v1h12V4H6zm0 6v1h11v-1H6zM1 4.5C1 3.672 1.666 3 2.5 3 3.328 3 4 3.666 4 4.5 4 5.328 3.334 6 2.5 6 1.672 6 1 5.334 1 4.5zm0 6C1 9.672 1.666 9 2.5 9c.828 0 1.5.666 1.5 1.5 0 .828-.666 1.5-1.5 1.5-.828 0-1.5-.666-1.5-1.5zm0 6c0-.828.666-1.5 1.5-1.5.828 0 1.5.666 1.5 1.5 0 .828-.666 1.5-1.5 1.5-.828 0-1.5-.666-1.5-1.5z" fill="#454545" fill-rule="evenodd"/></svg>
+<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M7 5.75c0 .414.336.75.75.75h9.5a.75.75 0 1 0 0-1.5h-9.5a.75.75 0 0 0-.75.75zm-6 0C1 4.784 1.777 4 2.75 4c.966 0 1.75.777 1.75 1.75 0 .966-.777 1.75-1.75 1.75C1.784 7.5 1 6.723 1 5.75zm6 9c0 .414.336.75.75.75h9.5a.75.75 0 1 0 0-1.5h-9.5a.75.75 0 0 0-.75.75zm-6 0c0-.966.777-1.75 1.75-1.75.966 0 1.75.777 1.75 1.75 0 .966-.777 1.75-1.75 1.75-.966 0-1.75-.777-1.75-1.75z" fill="#454545" fill-rule="evenodd"/></svg>

+ 1 - 1
packages/ckeditor5-list/theme/icons/numberedlist.svg

@@ -1 +1 @@
-<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M7 17h10v-1H7v1zM6 4v1h13V4H6zm1 6v1h11v-1H7zM2 2.5c0 .277.223.5.5.5H3v3.5a.499.499 0 1 0 1 0v-4c0-.277-.223-.5-.5-.5h-1c-.277 0-.5.223-.5.5zM2.5 8a.499.499 0 1 0 0 1H4v1H2.5c-.277 0-.5.223-.5.5v2c0 .277.223.5.5.5h2a.499.499 0 1 0 0-1H3v-1h1.5a.46.46 0 0 0 .188-.031.45.45 0 0 0 .28-.281A.461.461 0 0 0 5 10.5v-2a.46.46 0 0 0-.031-.187.45.45 0 0 0-.282-.282.463.463 0 0 0-.125-.03H2.5V8zm2.352 10.853a.493.493 0 0 0 .148-.35v-4.005A.493.493 0 0 0 4.505 14h-2.01a.494.494 0 0 0-.495.5c0 .268.222.5.495.5H4v1H2.495a.494.494 0 0 0-.495.5c0 .268.222.5.495.5H4v1H2.495a.494.494 0 0 0-.495.5c0 .268.222.5.495.5h2.01a.49.49 0 0 0 .351-.146z" fill="#454545" fill-rule="evenodd"/></svg>
+<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M7 5.75c0 .414.336.75.75.75h9.5a.75.75 0 1 0 0-1.5h-9.5a.75.75 0 0 0-.75.75zM3.5 3v5H2V3.7H1v-1h2.5V3zM.343 17.857l2.59-3.257H2.92a.6.6 0 1 0-1.04 0H.302a2 2 0 1 1 3.995 0h-.001c-.048.405-.16.734-.333.988-.175.254-.59.692-1.244 1.312H4.3v1h-4l.043-.043zM7 14.75a.75.75 0 0 1 .75-.75h9.5a.75.75 0 1 1 0 1.5h-9.5a.75.75 0 0 1-.75-.75z" fill="#454545" fill-rule="evenodd"/></svg>

Некоторые файлы не были показаны из-за большого количества измененных файлов