This commit is contained in:
parent
643ec5e98c
commit
08eca99c16
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,3 +6,4 @@ editor/node_modules/*
|
||||||
dist/
|
dist/
|
||||||
_links.json
|
_links.json
|
||||||
_documents.json
|
_documents.json
|
||||||
|
list-editor/node_modules
|
||||||
|
|
84
editor/package-lock.json
generated
84
editor/package-lock.json
generated
|
@ -1454,11 +1454,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"crossvent": {
|
"crossvent": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/crossvent/-/crossvent-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/crossvent/-/crossvent-1.5.5.tgz",
|
||||||
"integrity": "sha1-2ixPj0DJR4JRe/K+7BBEFIGUq5I=",
|
"integrity": "sha1-rSCHjkkh6b5z2daXb4suzQ9xoLE=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"custom-event": "1.0.0"
|
"custom-event": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"crypto-browserify": {
|
"crypto-browserify": {
|
||||||
|
@ -1555,9 +1555,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"custom-event": {
|
"custom-event": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
|
||||||
"integrity": "sha1-LkYovhncSyFLXAJjDFlx6BFhgGI="
|
"integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU="
|
||||||
},
|
},
|
||||||
"cyclist": {
|
"cyclist": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -2129,12 +2129,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dragula": {
|
"dragula": {
|
||||||
"version": "3.7.2",
|
"version": "3.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/dragula/-/dragula-3.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/dragula/-/dragula-3.7.3.tgz",
|
||||||
"integrity": "sha1-SjXJ05gf+sGpScKcpyhQWOhzk84=",
|
"integrity": "sha512-/rRg4zRhcpf81TyDhaHLtXt6sEywdfpv1cRUMeFFy7DuypH2U0WUL0GTdyAQvXegviT4PJK4KuMmOaIDpICseQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"contra": "1.9.4",
|
"contra": "1.9.4",
|
||||||
"crossvent": "1.5.4"
|
"crossvent": "1.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"duplexify": {
|
"duplexify": {
|
||||||
|
@ -9317,14 +9317,70 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wiki-list-editor": {
|
"wiki-list-editor": {
|
||||||
"version": "0.8.12",
|
"version": "file:../list-editor",
|
||||||
"resolved": "https://registry.npmjs.org/wiki-list-editor/-/wiki-list-editor-0.8.12.tgz",
|
|
||||||
"integrity": "sha512-1YdtzQv38WdbtyGFV9DAO5bJi7ndcyQlFXG64aJRimbguu6x1YRP8dzGiStGKfoVqZpR5zZ8Q+7HrqsxKVb24g==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"dragula": "^3.7.2",
|
"dragula": "^3.7.2",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.5.1",
|
||||||
"lodash": "^4.17.19"
|
"lodash": "^4.17.19"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"atoa": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/atoa/-/atoa-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-DMDpGkgOc4+SPrwQNnZHF3mzSkk="
|
||||||
|
},
|
||||||
|
"contra": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/contra/-/contra-1.9.4.tgz",
|
||||||
|
"integrity": "sha1-9TveQtfltZhcrk2ZqNYQUm3o8o0=",
|
||||||
|
"requires": {
|
||||||
|
"atoa": "1.0.0",
|
||||||
|
"ticky": "1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"crossvent": {
|
||||||
|
"version": "1.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/crossvent/-/crossvent-1.5.5.tgz",
|
||||||
|
"integrity": "sha1-rSCHjkkh6b5z2daXb4suzQ9xoLE=",
|
||||||
|
"requires": {
|
||||||
|
"custom-event": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"custom-event": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU="
|
||||||
|
},
|
||||||
|
"dragula": {
|
||||||
|
"version": "3.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dragula/-/dragula-3.7.3.tgz",
|
||||||
|
"integrity": "sha512-/rRg4zRhcpf81TyDhaHLtXt6sEywdfpv1cRUMeFFy7DuypH2U0WUL0GTdyAQvXegviT4PJK4KuMmOaIDpICseQ==",
|
||||||
|
"requires": {
|
||||||
|
"contra": "1.9.4",
|
||||||
|
"crossvent": "1.5.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"he": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||||
|
},
|
||||||
|
"jquery": {
|
||||||
|
"version": "3.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
|
||||||
|
"integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||||
|
},
|
||||||
|
"ticky": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ticky/-/ticky-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-t8+nHnaPHJAAxJe5FRswlHxQ5G0="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"window-size": {
|
"window-size": {
|
||||||
|
|
|
@ -1,16 +1,4 @@
|
||||||
{
|
{
|
||||||
"devDependencies": {
|
|
||||||
"clean-webpack-plugin": "^3.0.0",
|
|
||||||
"esm": "^3.2.25",
|
|
||||||
"html-webpack-plugin": "^3.2.0",
|
|
||||||
"mini-css-extract-plugin": "^0.9.0",
|
|
||||||
"mocha": "^7.2.0",
|
|
||||||
"mocha-webpack": "^1.1.0",
|
|
||||||
"scss-loader": "0.0.1",
|
|
||||||
"webpack": "^4.43.0",
|
|
||||||
"webpack-cli": "^3.3.7",
|
|
||||||
"webpack-dev-server": "^3.11.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@egjs/hammerjs": "^2.0.17",
|
"@egjs/hammerjs": "^2.0.17",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
|
@ -18,9 +6,12 @@
|
||||||
"clipboard": "^2.0.6",
|
"clipboard": "^2.0.6",
|
||||||
"copy-text-to-clipboard": "^2.2.0",
|
"copy-text-to-clipboard": "^2.2.0",
|
||||||
"css-loader": "^3.2.0",
|
"css-loader": "^3.2.0",
|
||||||
|
"dragula": "^3.7.3",
|
||||||
"extract-text-webpack-plugin": "^3.0.2",
|
"extract-text-webpack-plugin": "^3.0.2",
|
||||||
"file-loader": "^6.0.0",
|
"file-loader": "^6.0.0",
|
||||||
"fuse.js": "^6.0.0",
|
"fuse.js": "^6.0.0",
|
||||||
|
"he": "^1.2.0",
|
||||||
|
"jquery": "^3.5.1",
|
||||||
"jquery-contextmenu": "^2.9.2",
|
"jquery-contextmenu": "^2.9.2",
|
||||||
"keycharm": "^0.3.1",
|
"keycharm": "^0.3.1",
|
||||||
"lodash": ">=4.17.19",
|
"lodash": ">=4.17.19",
|
||||||
|
@ -42,7 +33,19 @@
|
||||||
"vis-data": "^6.6.1",
|
"vis-data": "^6.6.1",
|
||||||
"vis-network": "^7.6.10",
|
"vis-network": "^7.6.10",
|
||||||
"vis-util": "^4.3.2",
|
"vis-util": "^4.3.2",
|
||||||
"wiki-list-editor": "^0.8.12"
|
"wiki-list-editor": "file:../list-editor"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
|
"esm": "^3.2.25",
|
||||||
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
|
"mocha": "^7.2.0",
|
||||||
|
"mocha-webpack": "^1.1.0",
|
||||||
|
"scss-loader": "0.0.1",
|
||||||
|
"webpack": "^4.43.0",
|
||||||
|
"webpack-cli": "^3.3.7",
|
||||||
|
"webpack-dev-server": "^3.11.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node_modules/.bin/mocha -r esm",
|
"test": "node_modules/.bin/mocha -r esm",
|
||||||
|
|
|
@ -67,7 +67,7 @@ function Indicator(element, timeout) {
|
||||||
return {
|
return {
|
||||||
done() {
|
done() {
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
element.classList.add('hidden')
|
element.classList.add('hide')
|
||||||
}, timeout * 1000);
|
}, timeout * 1000);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ function Indicator(element, timeout) {
|
||||||
timeoutId = null;
|
timeoutId = null;
|
||||||
}
|
}
|
||||||
element.innerText = text;
|
element.innerText = text;
|
||||||
element.classList.remove('hidden')
|
element.classList.remove('hide')
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,9 +216,8 @@ MD.use(MarkdownItWikilinks({
|
||||||
|
|
||||||
let holders = document.getElementsByClassName('wiki-list-editor');
|
let holders = document.getElementsByClassName('wiki-list-editor');
|
||||||
|
|
||||||
_.forEach(holders, (item, i) => {
|
_.forEach(holders, async (item, i) => {
|
||||||
console.log(i, item)
|
new Editor(item).then(editor => editor.start());
|
||||||
let EDITOR = new Editor(item)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function Editor(holder) {
|
function Editor(holder) {
|
||||||
|
@ -291,7 +290,9 @@ function Editor(holder) {
|
||||||
renderGraphs();
|
renderGraphs();
|
||||||
})
|
})
|
||||||
|
|
||||||
createPageSearch().then(function ({titleSearch, commandSearch, commands}) {
|
menu.connectContextMenu(editor)
|
||||||
|
|
||||||
|
return createPageSearch().then(function ({titleSearch, commandSearch, commands}) {
|
||||||
editor.on('start-editing', function (input) {
|
editor.on('start-editing', function (input) {
|
||||||
const $lc = $('#link-complete');
|
const $lc = $('#link-complete');
|
||||||
|
|
||||||
|
@ -471,7 +472,6 @@ function Editor(holder) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
editor.on('stop-editing', function (input) {
|
editor.on('stop-editing', function (input) {
|
||||||
$(input).parents('.list-item').removeClass('active');
|
$(input).parents('.list-item').removeClass('active');
|
||||||
$('#link-complete').off()
|
$('#link-complete').off()
|
||||||
|
@ -479,11 +479,8 @@ function Editor(holder) {
|
||||||
mermaid.init()
|
mermaid.init()
|
||||||
renderGraphs();
|
renderGraphs();
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
menu.connectContextMenu(editor)
|
|
||||||
|
|
||||||
return editor
|
return editor
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeout = null;
|
let timeout = null;
|
||||||
|
|
128
list-editor/CHANGELOG.md
Normal file
128
list-editor/CHANGELOG.md
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.8.13] - 2020-09-03
|
||||||
|
|
||||||
|
* Allow multiple editors on the same page.
|
||||||
|
|
||||||
|
## [0.8.12] - 2020-07-19
|
||||||
|
|
||||||
|
* Upgraded lodash to 4.17.19
|
||||||
|
* Fix typo in import function name
|
||||||
|
|
||||||
|
## [0.8.11] - 2020-06-28
|
||||||
|
|
||||||
|
* Fixed store saveTree, remove children from other items, by making a clone
|
||||||
|
|
||||||
|
## [0.8.10] - 2020-06-28
|
||||||
|
|
||||||
|
* Publish to Github and NPM
|
||||||
|
|
||||||
|
## [0.8.9] - 2020-06-28
|
||||||
|
|
||||||
|
* Added improvements for CI
|
||||||
|
|
||||||
|
## [0.8.8] - 2020-06-28
|
||||||
|
|
||||||
|
* Added `saveTree` method with selection by id. This selects all children of
|
||||||
|
the element with `id`.
|
||||||
|
* Fixed bug where not all items where present in tree result of `saveTree`
|
||||||
|
|
||||||
|
## [0.8.7] - 2020-06-24
|
||||||
|
|
||||||
|
* Added `id` parameter to `update` method
|
||||||
|
|
||||||
|
## [0.8.6] - 2020-06-24
|
||||||
|
|
||||||
|
* Fixed update method of editor where the before object wasn't cloned, so
|
||||||
|
was always equal to replace object
|
||||||
|
|
||||||
|
## [0.8.5] - 2020-06-24
|
||||||
|
|
||||||
|
## [0.8.4] - 2020-06-24
|
||||||
|
|
||||||
|
* Added 'update' method to editor. It calls the 'update' method of store.
|
||||||
|
|
||||||
|
## [0.8.3] - 2020-06-23
|
||||||
|
|
||||||
|
* Added 'hidden' to default fields newListItem.
|
||||||
|
* Fix movement bug when moving up
|
||||||
|
|
||||||
|
## [0.8.2] - 2020-06-23
|
||||||
|
|
||||||
|
* Simplify indenting. Now we indent all children with a higher indent numbering.
|
||||||
|
|
||||||
|
## [0.8.1] - 2020-06-23
|
||||||
|
|
||||||
|
* Fix small bugs
|
||||||
|
|
||||||
|
## [0.8.0] - 2020-06-17
|
||||||
|
|
||||||
|
* Added rendering/rendered events
|
||||||
|
* Changed transform option, the second arguments is on element now
|
||||||
|
|
||||||
|
## [0.7.6] - 2020-06-15
|
||||||
|
|
||||||
|
* Remove the jump from the textarea with multiple lines when moving to it.
|
||||||
|
* Use the indent of the next item when it is larger then the current indent,
|
||||||
|
but use the current indent otherwise.
|
||||||
|
|
||||||
|
## [0.7.5] - 2020-06-11
|
||||||
|
|
||||||
|
* Add classes for borders when >= 1 indented
|
||||||
|
* Fixed bug where content was removed, when the marker was clicked on the
|
||||||
|
line with the active editor
|
||||||
|
|
||||||
|
## [0.7.4] - 2020-06-11
|
||||||
|
|
||||||
|
* Add .no-children, .open to .list-item, to simplify css
|
||||||
|
|
||||||
|
## [0.7.3] - 2020-06-09
|
||||||
|
|
||||||
|
* Remove mirroring of characters
|
||||||
|
|
||||||
|
## [0.7.2] - 2020-06-08
|
||||||
|
|
||||||
|
* Make links in content clickable
|
||||||
|
* Add "=" handling of selected text.
|
||||||
|
|
||||||
|
## [0.7.1] - 2020-06-08
|
||||||
|
|
||||||
|
* Add options
|
||||||
|
* Add transform function to options
|
||||||
|
|
||||||
|
## [0.7.0] - 2020-06-07
|
||||||
|
|
||||||
|
## [0.6.9] - 2020-06-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Add `saveTree` method that passes a tree-like structure as the first
|
||||||
|
argument.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Start new item opened
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/pstuifzand/list-editor/compare/0.8.13...HEAD
|
||||||
|
[0.8.13]: https://github.com/pstuifzand/list-editor/compare/0.8.12...0.8.13
|
||||||
|
[0.8.12]: https://github.com/pstuifzand/list-editor/compare/0.8.11...0.8.12
|
||||||
|
[0.8.11]: https://github.com/pstuifzand/list-editor/compare/0.8.10...0.8.11
|
||||||
|
[0.8.10]: https://github.com/pstuifzand/list-editor/compare/0.8.9...0.8.10
|
||||||
|
[0.8.9]: https://github.com/pstuifzand/list-editor/compare/0.8.8...0.8.9
|
||||||
|
[0.8.8]: https://github.com/pstuifzand/list-editor/compare/0.8.7...0.8.8
|
||||||
|
[0.8.7]: https://github.com/pstuifzand/list-editor/compare/0.8.6...0.8.7
|
||||||
|
[0.8.6]: https://github.com/pstuifzand/list-editor/compare/0.8.5...0.8.6
|
||||||
|
[0.8.5]: https://github.com/pstuifzand/list-editor/compare/0.8.4...0.8.5
|
||||||
|
[0.8.4]: https://github.com/pstuifzand/list-editor/compare/0.8.3...0.8.4
|
||||||
|
[0.8.3]: https://github.com/pstuifzand/list-editor/compare/0.8.2...0.8.3
|
||||||
|
[0.8.2]: https://github.com/pstuifzand/list-editor/compare/0.8.1...0.8.2
|
||||||
|
[0.8.1]: https://github.com/pstuifzand/list-editor/compare/0.8.0...0.8.1
|
||||||
|
[0.8.0]: https://github.com/pstuifzand/list-editor/compare/0.7.8...0.8.0
|
||||||
|
[0.7.8]: https://github.com/pstuifzand/list-editor/compare/0.7.7...0.7.8
|
25
list-editor/README.md
Normal file
25
list-editor/README.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
![Node.js Package](https://github.com/pstuifzand/list-editor/workflows/Node.js%20Package/badge.svg)
|
||||||
|
|
||||||
|
# Wiki List Editor
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First install the package.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install wiki-list-editor --save
|
||||||
|
```
|
||||||
|
|
||||||
|
And then use it in your javascript code.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import editor from 'wiki-list-editor';
|
||||||
|
|
||||||
|
let div = document.createElement('div')
|
||||||
|
let listEditor = editor(div);
|
||||||
|
document.body.appendChild(div);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Peter Stuifzand <peter@p83.nl>
|
58
list-editor/cursor.js
Normal file
58
list-editor/cursor.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
function createCursor(start) {
|
||||||
|
let cursor = start;
|
||||||
|
|
||||||
|
return {
|
||||||
|
get() {
|
||||||
|
return cursor;
|
||||||
|
},
|
||||||
|
set(newPosition) {
|
||||||
|
cursor = newPosition;
|
||||||
|
},
|
||||||
|
getId(store) {
|
||||||
|
return store.currentID(cursor)
|
||||||
|
},
|
||||||
|
atFirst() {
|
||||||
|
return cursor === 0;
|
||||||
|
},
|
||||||
|
atPosition(other) {
|
||||||
|
return cursor === other;
|
||||||
|
},
|
||||||
|
atEnd(store) {
|
||||||
|
return cursor === store.length()
|
||||||
|
},
|
||||||
|
hasMoved(saved) {
|
||||||
|
return cursor !== saved.get()
|
||||||
|
},
|
||||||
|
getCurrent(store) {
|
||||||
|
let id = store.currentID(cursor)
|
||||||
|
return store.value(id)
|
||||||
|
},
|
||||||
|
getCurrentElement(elements) {
|
||||||
|
return elements.slice(cursor, cursor + 1);
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
return createCursor(cursor);
|
||||||
|
},
|
||||||
|
moveUp(store) {
|
||||||
|
cursor = store.prevCursorPosition(cursor)
|
||||||
|
},
|
||||||
|
moveDown(store) {
|
||||||
|
cursor = store.nextCursorPosition(cursor, true)
|
||||||
|
},
|
||||||
|
remove(store) {
|
||||||
|
store.remove(cursor, 1)
|
||||||
|
},
|
||||||
|
insertAbove(store, item) {
|
||||||
|
store.insertBefore(store.currentID(cursor), item)
|
||||||
|
},
|
||||||
|
insertBelow(store, item) {
|
||||||
|
let id = store.insertAfter(store.currentID(cursor), item)
|
||||||
|
cursor = store.index(id)
|
||||||
|
},
|
||||||
|
forwardToNextVisible(store) {
|
||||||
|
cursor = store.nextCursorPosition(cursor, false)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createCursor;
|
500
list-editor/index.js
Normal file
500
list-editor/index.js
Normal file
|
@ -0,0 +1,500 @@
|
||||||
|
import _ from 'lodash'
|
||||||
|
import $ from 'jquery'
|
||||||
|
import he from 'he'
|
||||||
|
import textareaAutosizeInit from "./textarea.autosize"
|
||||||
|
import dragula from 'dragula'
|
||||||
|
import createCursor from './cursor'
|
||||||
|
import createSelection from './selection'
|
||||||
|
import Store from './store'
|
||||||
|
|
||||||
|
textareaAutosizeInit($)
|
||||||
|
|
||||||
|
function editor(root, inputData, options) {
|
||||||
|
root.classList.add('root')
|
||||||
|
|
||||||
|
let cursor = createCursor()
|
||||||
|
let selection = createSelection()
|
||||||
|
|
||||||
|
let defaults = {
|
||||||
|
transform(text, element) {
|
||||||
|
element.html(he.encode(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options = _.merge(defaults, options)
|
||||||
|
|
||||||
|
let drake = null;
|
||||||
|
|
||||||
|
function createStore(inputData) {
|
||||||
|
let data = [
|
||||||
|
{indented: 0, text: '', fold: 'open'},
|
||||||
|
];
|
||||||
|
if (inputData.length) {
|
||||||
|
data = inputData
|
||||||
|
}
|
||||||
|
return Store(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let store = createStore(inputData);
|
||||||
|
|
||||||
|
let events = {
|
||||||
|
change: [],
|
||||||
|
'start-editing': [],
|
||||||
|
'stop-editing': [],
|
||||||
|
'rendering': [],
|
||||||
|
'rendered': []
|
||||||
|
}
|
||||||
|
|
||||||
|
let editing = false
|
||||||
|
let currentEditor = null;
|
||||||
|
|
||||||
|
function newListItem(indented) {
|
||||||
|
return {indented: indented, text: '', fold: 'open', hidden: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
function newItem(value) {
|
||||||
|
let el = $('<div class="list-item">')
|
||||||
|
.data('id', value.id)
|
||||||
|
.data('indented', value.indented)
|
||||||
|
.css('margin-left', (value.indented * 32) + 'px')
|
||||||
|
let line = $('<div class="line">')
|
||||||
|
let content = $('<div class="content">')
|
||||||
|
line.prepend(content)
|
||||||
|
options.transform(value.text, content)
|
||||||
|
line.prepend($('<span class="marker"></span>'))
|
||||||
|
line.prepend($('<span class="fold">▶</span>'))
|
||||||
|
el.prepend(line)
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Element} rootElement
|
||||||
|
* @param rootData
|
||||||
|
*/
|
||||||
|
function render(rootElement, rootData) {
|
||||||
|
trigger('rendering')
|
||||||
|
|
||||||
|
let first = 0;
|
||||||
|
let last = rootData.length();
|
||||||
|
|
||||||
|
let elements = $(rootElement).children('div.list-item');
|
||||||
|
|
||||||
|
let $enter = elements.slice(first, last);
|
||||||
|
let enterData = rootData.slice(first, $enter.length);
|
||||||
|
|
||||||
|
let exitData = rootData.slice($enter.length);
|
||||||
|
let $exitEl = elements.slice($enter.length)
|
||||||
|
|
||||||
|
let hideLevel = 99999;
|
||||||
|
|
||||||
|
$enter.each(function (index, li) {
|
||||||
|
let storeId = enterData[index]
|
||||||
|
let value = rootData.value(storeId)
|
||||||
|
|
||||||
|
let hasChildren = false;
|
||||||
|
if (index + 1 < last) {
|
||||||
|
let next = rootData.afterValue(storeId)
|
||||||
|
hasChildren = next && (value.indented < next.indented)
|
||||||
|
}
|
||||||
|
|
||||||
|
let $li = $(li).data('id', value.id)
|
||||||
|
.toggleClass('selected', cursor.atPosition(index))
|
||||||
|
.toggleClass('selection-first', selection.isSelectedFirst(index))
|
||||||
|
.toggleClass('selection-last', selection.isSelectedLast(index))
|
||||||
|
.toggleClass('selection', selection.isSelected(index))
|
||||||
|
.toggleClass('hidden', value.indented >= hideLevel)
|
||||||
|
.toggleClass('border', value.indented >= 1)
|
||||||
|
.css('margin-left', (value.indented * 32) + 'px')
|
||||||
|
.find('.content')
|
||||||
|
value.hidden = value.indented >= hideLevel
|
||||||
|
|
||||||
|
options.transform(value.text, $li)
|
||||||
|
|
||||||
|
if (value.indented < hideLevel) {
|
||||||
|
if (value.fold !== 'open') {
|
||||||
|
hideLevel = value.indented + 1
|
||||||
|
} else {
|
||||||
|
hideLevel = 99999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.fold', $(li))
|
||||||
|
.toggleClass('open', value.fold === 'open')
|
||||||
|
.toggleClass('no-children', !hasChildren)
|
||||||
|
|
||||||
|
$(li).toggleClass('no-children', !hasChildren)
|
||||||
|
.toggleClass('open', value.fold === 'open')
|
||||||
|
});
|
||||||
|
|
||||||
|
_.each(exitData, function (storeId, index) {
|
||||||
|
let value = rootData.value(storeId)
|
||||||
|
let $li = newItem(value)
|
||||||
|
.css('margin-left', (value.indented * 32) + 'px')
|
||||||
|
.toggleClass('selection-first', selection.isSelectedFirst(index))
|
||||||
|
.toggleClass('selection-last', selection.isSelectedLast(index))
|
||||||
|
.toggleClass('selection', selection.isSelected(index))
|
||||||
|
.toggleClass('selected', cursor.atPosition(index + $enter.length))
|
||||||
|
.toggleClass('border', value.indented >= 1)
|
||||||
|
.toggleClass('hidden', value.indented >= hideLevel);
|
||||||
|
value.hidden = value.indented >= hideLevel
|
||||||
|
|
||||||
|
let hasChildren = false;
|
||||||
|
if (enterData.length + index + 1 < last) {
|
||||||
|
let next = rootData.afterValue(storeId)
|
||||||
|
hasChildren = next && (value.indented < next.indented)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.indented < hideLevel) {
|
||||||
|
if (value.fold === 'open') {
|
||||||
|
hideLevel = 99999;
|
||||||
|
} else {
|
||||||
|
hideLevel = value.indented + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.fold', $li)
|
||||||
|
.toggleClass('open', value.fold === 'open')
|
||||||
|
.toggleClass('no-children', !hasChildren)
|
||||||
|
|
||||||
|
$li.toggleClass('no-children', !hasChildren)
|
||||||
|
.toggleClass('open', value.fold === 'open')
|
||||||
|
|
||||||
|
$(rootElement).append($li)
|
||||||
|
})
|
||||||
|
|
||||||
|
$exitEl.remove()
|
||||||
|
|
||||||
|
trigger('rendered')
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableDragging(drake) {
|
||||||
|
if (drake) drake.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableDragging(rootElement) {
|
||||||
|
let drake = dragula([rootElement], {
|
||||||
|
moves: function (el, container, handle) {
|
||||||
|
return handle.classList.contains('marker')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let start = -1;
|
||||||
|
let startID = null;
|
||||||
|
|
||||||
|
drake.on('drag', function (el, source) {
|
||||||
|
startID = $(el).data('id')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
drake.on('drop', function (el, target, source, sibling) {
|
||||||
|
let stopID = $(sibling).data('id')
|
||||||
|
if (startID === stopID) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = store.moveBefore(startID, stopID)
|
||||||
|
|
||||||
|
let position = store.index(id);
|
||||||
|
cursor.set(position)
|
||||||
|
selection.selectOne(position, store)
|
||||||
|
|
||||||
|
trigger('change')
|
||||||
|
})
|
||||||
|
return drake;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopEditing(rootElement, store, element) {
|
||||||
|
if (!editing) return
|
||||||
|
if (element === null) {
|
||||||
|
editing = false
|
||||||
|
currentEditor = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = element.val()
|
||||||
|
$(element).closest('.list-item').removeClass('editor');
|
||||||
|
store.update(element.data('id'), (value) => {
|
||||||
|
return _.merge(value, {
|
||||||
|
text: text
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
trigger('change')
|
||||||
|
|
||||||
|
let $span = $('<div class="content">');
|
||||||
|
options.transform(text, $span)
|
||||||
|
element.replaceWith($span);
|
||||||
|
trigger('stop-editing', currentEditor[0])
|
||||||
|
editing = false
|
||||||
|
currentEditor = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Element} rootElement
|
||||||
|
* @param {Store} store
|
||||||
|
* @param cursor
|
||||||
|
* @returns {jQuery|HTMLElement}
|
||||||
|
*/
|
||||||
|
function startEditing(rootElement, store, cursor) {
|
||||||
|
if (editing) return
|
||||||
|
editing = true
|
||||||
|
|
||||||
|
let elements = $(rootElement).children('div.list-item');
|
||||||
|
let $textarea = $('<textarea rows=1 class="input-line">');
|
||||||
|
$textarea.val(cursor.getCurrent(store).text).trigger('input')
|
||||||
|
let currentElement = cursor.getCurrentElement(elements);
|
||||||
|
currentElement.find('.content').replaceWith($textarea)
|
||||||
|
currentElement.addClass('editor');
|
||||||
|
$textarea.focus()
|
||||||
|
$textarea.data(cursor.getCurrent(store))
|
||||||
|
$textarea.textareaAutoSize()
|
||||||
|
currentEditor = $textarea
|
||||||
|
trigger('start-editing', currentEditor[0])
|
||||||
|
return $textarea
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
resolve(store.debug().result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveTree(from) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
resolve(store.tree(from))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy(element, opt) {
|
||||||
|
let item = $(element).parents('.list-item')
|
||||||
|
let id = item.data('id')
|
||||||
|
|
||||||
|
if (opt.recursive) {
|
||||||
|
return saveTree(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
resolve(store.value(id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function on(evt, handler) {
|
||||||
|
events[evt].push(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
function trigger(event) {
|
||||||
|
let args = [...arguments]
|
||||||
|
args.splice(0, 1)
|
||||||
|
_.each(events[event], function (handler) {
|
||||||
|
handler(...args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
disableDragging(drake)
|
||||||
|
render(root, store);
|
||||||
|
drake = enableDragging(root)
|
||||||
|
|
||||||
|
cursor.set(0)
|
||||||
|
startEditing(root, store, cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
$(root).on('paste', '.input-line', function (event) {
|
||||||
|
let tag = event.target.tagName.toLowerCase();
|
||||||
|
if (tag === 'textarea' && currentEditor[0].value.substring(0, 3) === '```') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let parentItem = $(this).parents('.list-item')
|
||||||
|
let index = $(root).children('div.list-item').index(parentItem)
|
||||||
|
let pastedData = event.originalEvent.clipboardData.getData('text')
|
||||||
|
let lines = pastedData.split(/\n/)
|
||||||
|
if (lines.length === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentID = store.currentID(index);
|
||||||
|
let baseIndent = store.value(currentID).indented
|
||||||
|
|
||||||
|
let newItems = _.filter(_.map(lines, function (line) {
|
||||||
|
if (line.length === 0) return;
|
||||||
|
let matches = line.match(/(\s{4})/g)
|
||||||
|
let relIndent = matches ? matches.length : 0;
|
||||||
|
let newItem = newListItem(baseIndent + relIndent)
|
||||||
|
newItem.text = line.replace(/^\s+/, '')
|
||||||
|
return newItem
|
||||||
|
}))
|
||||||
|
store.insertAfter(currentID, ...newItems)
|
||||||
|
|
||||||
|
disableDragging(drake)
|
||||||
|
render(root, store);
|
||||||
|
drake = enableDragging(root)
|
||||||
|
return false
|
||||||
|
});
|
||||||
|
|
||||||
|
$(root).on('keydown', '.input-line', function (event) {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
stopEditing(root, store, $(this))
|
||||||
|
selection.selectOne(cursor.get(), store)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
});
|
||||||
|
|
||||||
|
$(root).on('keydown', function (event) {
|
||||||
|
let tag = event.target.tagName.toLowerCase();
|
||||||
|
if (tag === 'textarea' && currentEditor[0].value.substring(0, 3) === '```') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let next = true
|
||||||
|
let prevSelected = cursor.save();
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
cursor.moveUp(store);
|
||||||
|
if (event.shiftKey) {
|
||||||
|
selection.include(cursor.get(), store)
|
||||||
|
} else {
|
||||||
|
selection.selectNothing(cursor.get())
|
||||||
|
}
|
||||||
|
next = false
|
||||||
|
} else if (event.key === 'ArrowDown') {
|
||||||
|
cursor.moveDown(store);
|
||||||
|
if (event.shiftKey) {
|
||||||
|
selection.include(cursor.get(), store)
|
||||||
|
} else {
|
||||||
|
selection.selectNothing(cursor.get())
|
||||||
|
}
|
||||||
|
next = false
|
||||||
|
} else if (event.shiftKey && event.key === 'Delete') {
|
||||||
|
stopEditing(root, store, currentEditor);
|
||||||
|
if (selection.hasSelection()) {
|
||||||
|
selection.remove(store)
|
||||||
|
// FIXME: adjust cursor
|
||||||
|
} else {
|
||||||
|
cursor.remove(store)
|
||||||
|
}
|
||||||
|
next = false
|
||||||
|
|
||||||
|
trigger('change');
|
||||||
|
} else if (event.key === 'Enter') {
|
||||||
|
stopEditing(root, store, currentEditor);
|
||||||
|
next = false
|
||||||
|
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
let id = store.currentID(cursor.get())
|
||||||
|
let current = store.value(id)
|
||||||
|
let indent = current.indented
|
||||||
|
let item = newListItem(indent)
|
||||||
|
cursor.insertAbove(store, item)
|
||||||
|
} else {
|
||||||
|
let insertion = cursor.save()
|
||||||
|
let currentValue = store.value(store.currentID(cursor.get()));
|
||||||
|
let current = currentValue ? currentValue.indented : 0
|
||||||
|
let next = cursor.get() + 1 < store.length() ? store.value(store.currentID(cursor.get() + 1)).indented : current
|
||||||
|
let indent = next > current ? next : current
|
||||||
|
let item = newListItem(indent)
|
||||||
|
cursor.insertBelow(store, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
selection.selectOne(cursor.get(), store)
|
||||||
|
trigger('change')
|
||||||
|
} else if (event.key === 'Tab') {
|
||||||
|
store.indent(cursor.get(), store.lastHigherIndented(cursor.get()) - cursor.get(), event.shiftKey ? -1 : 1)
|
||||||
|
next = false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
disableDragging(drake)
|
||||||
|
render(root, store);
|
||||||
|
drake = enableDragging(root)
|
||||||
|
|
||||||
|
if (cursor.hasMoved(prevSelected)) {
|
||||||
|
if (!selection.hasSelection() && editing && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {
|
||||||
|
stopEditing(root, store, currentEditor);
|
||||||
|
startEditing(root, store, cursor);
|
||||||
|
return false
|
||||||
|
} else if (selection.hasSelection()) {
|
||||||
|
stopEditing(root, store, currentEditor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
startEditing(root, store, cursor);
|
||||||
|
return false
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
stopEditing(root, store, currentEditor);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
$(root).on('click', '.marker', function () {
|
||||||
|
stopEditing(root, store, $(this).next('textarea'));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$(root).on('click', '.content a', function (event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
$(root).on('click', '.list-item', function () {
|
||||||
|
let currentIndex = $(root).children('div.list-item').index(this)
|
||||||
|
if (cursor.atPosition(currentIndex) && currentEditor !== null && currentEditor.closest('.list-item')[0] === this) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
stopEditing(root, store, currentEditor)
|
||||||
|
|
||||||
|
cursor.set(currentIndex)
|
||||||
|
selection.selectOne(cursor.get(), store)
|
||||||
|
|
||||||
|
disableDragging(drake)
|
||||||
|
render(root, store);
|
||||||
|
drake = enableDragging(root)
|
||||||
|
|
||||||
|
const $input = startEditing(root, store, cursor)
|
||||||
|
$input.trigger('input')
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
$(root).on('click', '.fold', function () {
|
||||||
|
let open = !$(this).hasClass('open');
|
||||||
|
$(this).toggleClass('open', open)
|
||||||
|
$(this).toggleClass('closed', !open)
|
||||||
|
|
||||||
|
let item = $(this).parents('.list-item')
|
||||||
|
let elements = $(root).children('div.list-item');
|
||||||
|
let index = elements.index(item)
|
||||||
|
store.update(item.data('id'), function (item) {
|
||||||
|
item.fold = open ? 'open' : 'closed'
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
|
||||||
|
disableDragging(drake)
|
||||||
|
render(root, store);
|
||||||
|
drake = enableDragging(root)
|
||||||
|
});
|
||||||
|
|
||||||
|
function update(id, callback) {
|
||||||
|
let changed = false
|
||||||
|
store.update(id, function (item, prev, next) {
|
||||||
|
let before = Object.assign({}, item)
|
||||||
|
item = callback(item, prev, next)
|
||||||
|
changed = item.text !== before.text || item.indented !== before.indented || item.fold !== before.fold
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
if (changed) {
|
||||||
|
trigger('change')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
on,
|
||||||
|
save,
|
||||||
|
saveTree,
|
||||||
|
copy,
|
||||||
|
update,
|
||||||
|
start
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default editor
|
174
list-editor/package-lock.json
generated
Normal file
174
list-editor/package-lock.json
generated
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
{
|
||||||
|
"name": "wiki-list-editor",
|
||||||
|
"version": "0.8.12",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"atoa": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/atoa/-/atoa-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-DMDpGkgOc4+SPrwQNnZHF3mzSkk="
|
||||||
|
},
|
||||||
|
"balanced-match": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"concat-map": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"contra": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/contra/-/contra-1.9.4.tgz",
|
||||||
|
"integrity": "sha1-9TveQtfltZhcrk2ZqNYQUm3o8o0=",
|
||||||
|
"requires": {
|
||||||
|
"atoa": "1.0.0",
|
||||||
|
"ticky": "1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"crossvent": {
|
||||||
|
"version": "1.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/crossvent/-/crossvent-1.5.4.tgz",
|
||||||
|
"integrity": "sha1-2ixPj0DJR4JRe/K+7BBEFIGUq5I=",
|
||||||
|
"requires": {
|
||||||
|
"custom-event": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"custom-event": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-LkYovhncSyFLXAJjDFlx6BFhgGI="
|
||||||
|
},
|
||||||
|
"dragula": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/dragula/-/dragula-3.7.2.tgz",
|
||||||
|
"integrity": "sha1-SjXJ05gf+sGpScKcpyhQWOhzk84=",
|
||||||
|
"requires": {
|
||||||
|
"contra": "1.9.4",
|
||||||
|
"crossvent": "1.5.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"esm": {
|
||||||
|
"version": "3.2.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
|
||||||
|
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"fs.realpath": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"glob": {
|
||||||
|
"version": "7.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||||
|
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.0.4",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"he": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||||
|
},
|
||||||
|
"inflight": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"jasmine": {
|
||||||
|
"version": "3.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.5.0.tgz",
|
||||||
|
"integrity": "sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"glob": "^7.1.4",
|
||||||
|
"jasmine-core": "~3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jasmine-core": {
|
||||||
|
"version": "3.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz",
|
||||||
|
"integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"jquery": {
|
||||||
|
"version": "3.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
|
||||||
|
"integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||||
|
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path-is-absolute": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"ticky": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ticky/-/ticky-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-t8+nHnaPHJAAxJe5FRswlHxQ5G0="
|
||||||
|
},
|
||||||
|
"wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
list-editor/package.json
Normal file
30
list-editor/package.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "wiki-list-editor",
|
||||||
|
"version": "0.8.13",
|
||||||
|
"description": "Simple editor of lists",
|
||||||
|
"author": "Peter Stuifzand <peter@p83.nl>",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "jasmine --require=esm"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"dragula": "^3.7.2",
|
||||||
|
"he": "^1.2.0",
|
||||||
|
"jquery": "^3.5.1",
|
||||||
|
"lodash": "^4.17.19"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"esm": "^3.2.25",
|
||||||
|
"jasmine": "^3.5.0"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+ssh://git@github.com/pstuifzand/list-editor.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/pstuifzand/list-editor/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/pstuifzand/list-editor#readme"
|
||||||
|
}
|
69
list-editor/selection.js
Normal file
69
list-editor/selection.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// consecutive for now
|
||||||
|
function createSelection() {
|
||||||
|
let first = -1;
|
||||||
|
let last = -1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
reset() {
|
||||||
|
first = -1
|
||||||
|
last = -1
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {int} f
|
||||||
|
* @param {Store} store
|
||||||
|
*/
|
||||||
|
selectOne(f, store) {
|
||||||
|
if (f > last) {
|
||||||
|
last = first
|
||||||
|
}
|
||||||
|
first = f
|
||||||
|
last = first + 1
|
||||||
|
},
|
||||||
|
|
||||||
|
selectNothing(f) {
|
||||||
|
first = f
|
||||||
|
last = f
|
||||||
|
},
|
||||||
|
|
||||||
|
remove(store) {
|
||||||
|
store.remove(first, last - first)
|
||||||
|
},
|
||||||
|
|
||||||
|
hasSelection() {
|
||||||
|
return first >= 0 && last >= 0 && first !== last
|
||||||
|
},
|
||||||
|
|
||||||
|
isSelected(line) {
|
||||||
|
return first >= 0 && last >= 0 && line >= first && line < last
|
||||||
|
},
|
||||||
|
|
||||||
|
isSelectedFirst(line) {
|
||||||
|
return first >= 0 && last >= 0 && line === first && line < last
|
||||||
|
},
|
||||||
|
|
||||||
|
isSelectedLast(line) {
|
||||||
|
return first >= 0 && last >= 0 && line >= first && line < last && line + 1 === last
|
||||||
|
},
|
||||||
|
|
||||||
|
indent(store, dir) {
|
||||||
|
store.indent(first, last - first, dir)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {int} index
|
||||||
|
* @param store
|
||||||
|
*/
|
||||||
|
include(index, store) {
|
||||||
|
first = Math.min(first, index)
|
||||||
|
last = Math.max(last, index + 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
debug() {
|
||||||
|
return {first, last}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default createSelection
|
50
list-editor/spec/cursor.spec.js
Normal file
50
list-editor/spec/cursor.spec.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import createCursor from '../cursor'
|
||||||
|
import createStore from '../store'
|
||||||
|
|
||||||
|
describe("A cursor", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.cursor = createCursor(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains a get method", function() {
|
||||||
|
expect(this.cursor.get()).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains a set method", function() {
|
||||||
|
this.cursor.set(4)
|
||||||
|
expect(this.cursor.get()).toBe(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains an atFirst method", function() {
|
||||||
|
expect(this.cursor.atFirst()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("with a store", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.store = createStore([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains an atEnd method", function () {
|
||||||
|
this.cursor.set(0)
|
||||||
|
expect(this.cursor.atEnd(this.store)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("with a store", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.store = createStore([
|
||||||
|
{indented:0, fold: 'open'},
|
||||||
|
{indented:1, fold: 'open'},
|
||||||
|
{indented:2, fold: 'open'},
|
||||||
|
{indented:3, fold: 'open'},
|
||||||
|
{indented:1, fold: 'open'},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("moveUp moves up by one", function() {
|
||||||
|
this.cursor.set(4)
|
||||||
|
this.cursor.moveUp(this.store)
|
||||||
|
expect(this.cursor.get()).toBe(3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
163
list-editor/spec/store.spec.js
Normal file
163
list-editor/spec/store.spec.js
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
import createStore from '../store'
|
||||||
|
|
||||||
|
describe("A store", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.store = createStore([
|
||||||
|
{text: "Hello", id: "_a", indented: 0}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains a length method", function () {
|
||||||
|
expect(this.store.length()).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains an append method", function () {
|
||||||
|
this.store.append({text: "1"})
|
||||||
|
expect(this.store.length()).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains an nextCursorPosition method", function () {
|
||||||
|
this.store.append({text: "1"})
|
||||||
|
expect(this.store.length()).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains an indent method", function () {
|
||||||
|
this.store.append({text: "1", indented: 0})
|
||||||
|
this.store.append({text: "2", indented: 1})
|
||||||
|
this.store.append({text: "3", indented: 2})
|
||||||
|
this.store.indent(1, 3, 1)
|
||||||
|
|
||||||
|
expect(this.store.value(this.store.currentID(0)).indented).toBe(0)
|
||||||
|
expect(this.store.value(this.store.currentID(1)).indented).toBe(1)
|
||||||
|
expect(this.store.value(this.store.currentID(2)).indented).toBe(2)
|
||||||
|
expect(this.store.value(this.store.currentID(3)).indented).toBe(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains a lastHigherIndented method", function () {
|
||||||
|
this.store.append({text: "1", indented: 0})
|
||||||
|
this.store.append({text: "2", indented: 1})
|
||||||
|
this.store.append({text: "3", indented: 2})
|
||||||
|
this.store.append({text: "3", indented: 0})
|
||||||
|
|
||||||
|
expect(this.store.lastHigherIndented(1)).toBe(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains a tree method that returns same indent items as a list", function () {
|
||||||
|
let store = createStore([
|
||||||
|
{text: "a", id: "_a", indented: 0},
|
||||||
|
{text: "b", id: "_b", indented: 0},
|
||||||
|
{text: "c", id: "_c", indented: 0},
|
||||||
|
{text: "d", id: "_d", indented: 0}
|
||||||
|
])
|
||||||
|
let tree = store.tree()
|
||||||
|
expect(tree).toEqual([
|
||||||
|
{text: "a", id: "_a", indented: 0},
|
||||||
|
{text: "b", id: "_b", indented: 0},
|
||||||
|
{text: "c", id: "_c", indented: 0},
|
||||||
|
{text: "d", id: "_d", indented: 0}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains a tree method that returns children as a list in children", function () {
|
||||||
|
let store = createStore([
|
||||||
|
{text: "a", id: "_a", indented: 0},
|
||||||
|
{text: "b", id: "_b", indented: 1},
|
||||||
|
{text: "c", id: "_c", indented: 1},
|
||||||
|
{text: "d", id: "_d", indented: 1}
|
||||||
|
])
|
||||||
|
let tree = store.tree()
|
||||||
|
expect(tree).toEqual([
|
||||||
|
{
|
||||||
|
text: "a", id: "_a", indented: 0, children: [
|
||||||
|
{text: "b", id: "_b", indented: 1},
|
||||||
|
{text: "c", id: "_c", indented: 1},
|
||||||
|
{text: "d", id: "_d", indented: 1}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains a tree method that returns multiple children as a list in children", function () {
|
||||||
|
let store = createStore([
|
||||||
|
{text: "a", id: "_a", indented: 0},
|
||||||
|
{text: "b", id: "_b", indented: 1},
|
||||||
|
{text: "c", id: "_c", indented: 0},
|
||||||
|
{text: "d", id: "_d", indented: 1}
|
||||||
|
])
|
||||||
|
let tree = store.tree()
|
||||||
|
expect(tree).toEqual([
|
||||||
|
{
|
||||||
|
text: "a", id: "_a", indented: 0, children: [
|
||||||
|
{text: "b", id: "_b", indented: 1},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "c", id: "_c", indented: 0, children: [
|
||||||
|
{text: "d", id: "_d", indented: 1}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains a tree method that transform 0,1,2", function () {
|
||||||
|
let store = createStore([
|
||||||
|
{text: "a", id: "_a", indented: 0},
|
||||||
|
{text: "b", id: "_b", indented: 1},
|
||||||
|
{text: "c", id: "_c", indented: 2},
|
||||||
|
])
|
||||||
|
let tree = store.tree()
|
||||||
|
expect(tree).toEqual([
|
||||||
|
{
|
||||||
|
text: "a", id: "_a", indented: 0, children: [
|
||||||
|
{
|
||||||
|
text: "b", id: "_b", indented: 1, children: [
|
||||||
|
{text: "c", id: "_c", indented: 2}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains a tree method that returns deeper children as a list in children", function () {
|
||||||
|
let store = createStore([
|
||||||
|
{text: "a", id: "_a", indented: 0},
|
||||||
|
{text: "b", id: "_b", indented: 1},
|
||||||
|
{text: "c", id: "_c", indented: 2},
|
||||||
|
{text: "d", id: "_d", indented: 0},
|
||||||
|
])
|
||||||
|
let tree = store.tree()
|
||||||
|
expect(tree).toEqual([
|
||||||
|
{
|
||||||
|
text: "a", id: "_a", indented: 0, children: [
|
||||||
|
{
|
||||||
|
text: "b", id: "_b", indented: 1, children: [
|
||||||
|
{text: "c", id: "_c", indented: 2},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{text: "d", id: "_d", indented: 0}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("contains a tree method that accepts a from argument", function () {
|
||||||
|
let store = createStore([
|
||||||
|
{text: "a", id: "_a", indented: 0},
|
||||||
|
{text: "b", id: "_b", indented: 1},
|
||||||
|
{text: "c", id: "_c", indented: 2},
|
||||||
|
{text: "d", id: "_d", indented: 0},
|
||||||
|
])
|
||||||
|
let tree = store.tree("_a")
|
||||||
|
expect(tree).toEqual([
|
||||||
|
{
|
||||||
|
text: "a", id: "_a", indented: 0, children: [
|
||||||
|
{
|
||||||
|
text: "b", id: "_b", indented: 1, children: [
|
||||||
|
{text: "c", id: "_c", indented: 2},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
11
list-editor/spec/support/jasmine.json
Normal file
11
list-editor/spec/support/jasmine.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"spec_dir": "spec",
|
||||||
|
"spec_files": [
|
||||||
|
"**/*[sS]pec.js"
|
||||||
|
],
|
||||||
|
"helpers": [
|
||||||
|
"helpers/**/*.js"
|
||||||
|
],
|
||||||
|
"stopSpecOnExpectationFailure": false,
|
||||||
|
"random": true
|
||||||
|
}
|
7
list-editor/spec/test.spec.js
Normal file
7
list-editor/spec/test.spec.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
|
||||||
|
describe("A suite", function() {
|
||||||
|
it("contains spec with an expectation", function() {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
393
list-editor/store.js
Normal file
393
list-editor/store.js
Normal file
|
@ -0,0 +1,393 @@
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: Store should contain all methods that work with items. At the moment
|
||||||
|
* there are still a few places where we change the items from the outside,
|
||||||
|
* while it's very important that the items behave a certain way.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Store(inputData) {
|
||||||
|
let idList = [];
|
||||||
|
let values = {};
|
||||||
|
|
||||||
|
let ID = function () {
|
||||||
|
return '_' + Math.random().toString(36).substr(2, 12);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {int} index
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function currentID(index) {
|
||||||
|
if (index === idList.length) {
|
||||||
|
return 'at-end'
|
||||||
|
}
|
||||||
|
return idList[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function index(id) {
|
||||||
|
return _.findIndex(idList, value => value === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
function value(id) {
|
||||||
|
return values[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} afterId
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
function afterValue(afterId) {
|
||||||
|
let i = index(afterId)
|
||||||
|
return values[idList[i + 1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevCursorPosition(cursor) {
|
||||||
|
let curIndent = values[idList[cursor]].indented
|
||||||
|
let curClosed = values[idList[cursor]].fold !== 'open';
|
||||||
|
if (!curClosed) {
|
||||||
|
curIndent = 10000000;
|
||||||
|
}
|
||||||
|
let moving = true
|
||||||
|
|
||||||
|
while (moving) {
|
||||||
|
cursor--
|
||||||
|
if (cursor < 0) {
|
||||||
|
cursor = idList.length - 1
|
||||||
|
curIndent = values[idList[cursor]].indented
|
||||||
|
}
|
||||||
|
let next = values[idList[cursor]];
|
||||||
|
if (curIndent >= next.indented && !next.hidden) {
|
||||||
|
moving = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the next 'open' position in the list.
|
||||||
|
*
|
||||||
|
* @param {number} cursor
|
||||||
|
* @param {bool} wrap
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function nextCursorPosition(cursor, wrap) {
|
||||||
|
let curIndent = values[idList[cursor]].indented
|
||||||
|
let curClosed = values[idList[cursor]].fold !== 'open';
|
||||||
|
if (!curClosed) {
|
||||||
|
curIndent = 10000000;
|
||||||
|
}
|
||||||
|
let moving = true
|
||||||
|
|
||||||
|
while (moving) {
|
||||||
|
cursor++
|
||||||
|
if (wrap) {
|
||||||
|
if (cursor >= idList.length) {
|
||||||
|
cursor = 0
|
||||||
|
curIndent = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return cursor
|
||||||
|
}
|
||||||
|
let next = values[idList[cursor]];
|
||||||
|
if (curIndent >= next.indented) {
|
||||||
|
moving = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} beforeId
|
||||||
|
* @param {object} item
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function insertBefore(beforeId, item) {
|
||||||
|
let index = _.findIndex(idList, (id) => id === beforeId)
|
||||||
|
let id = item.id
|
||||||
|
if (!id) {
|
||||||
|
let newId = ID()
|
||||||
|
item.id = newId
|
||||||
|
values[newId] = item
|
||||||
|
id = newId
|
||||||
|
}
|
||||||
|
idList.splice(index, 0, id)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} afterId
|
||||||
|
* @param {object} items
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function insertAfter(afterId, ...items) {
|
||||||
|
if (afterId === 'at-end') {
|
||||||
|
let newItems = _.map(items, item => {
|
||||||
|
return this.append(item)
|
||||||
|
})
|
||||||
|
return newItems[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = _.findIndex(idList, (id) => id === afterId)
|
||||||
|
|
||||||
|
let newItems = _.map(items, item => {
|
||||||
|
let id = item.id;
|
||||||
|
if (!id) {
|
||||||
|
let newId = ID()
|
||||||
|
item.id = newId
|
||||||
|
values[newId] = item
|
||||||
|
}
|
||||||
|
return item.id
|
||||||
|
})
|
||||||
|
idList.splice(index + 1, 0, ...newItems)
|
||||||
|
return newItems[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} item
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function append(item) {
|
||||||
|
let id = item.id;
|
||||||
|
if (!item.id || item.id[0] !== '_') {
|
||||||
|
let newId = ID();
|
||||||
|
item.id = newId
|
||||||
|
id = newId
|
||||||
|
}
|
||||||
|
delete item.children
|
||||||
|
values[id] = item;
|
||||||
|
idList.push(id)
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback updateCallback
|
||||||
|
* @param {object} item
|
||||||
|
* @param {object} prev
|
||||||
|
* @param {object} next
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {string} currentId
|
||||||
|
* @param {updateCallback} callback
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function update(currentId, callback) {
|
||||||
|
let index = _.findIndex(idList, (id) => id === currentId)
|
||||||
|
values[currentId] = callback(values[currentId], values[idList[index - 1]], values[idList[index + 1]])
|
||||||
|
return currentId
|
||||||
|
}
|
||||||
|
|
||||||
|
function length() {
|
||||||
|
return idList.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {{result: Array, values: {}, idList: []}}
|
||||||
|
*/
|
||||||
|
function debug() {
|
||||||
|
let result = _.map(idList, (id) => {
|
||||||
|
return values[id];
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
result,
|
||||||
|
idList,
|
||||||
|
values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(start, len) {
|
||||||
|
idList.splice(start, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {int} start
|
||||||
|
* @param {int} end
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
function slice(start, end) {
|
||||||
|
return idList.slice(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {int} first
|
||||||
|
* @param {int} len
|
||||||
|
* @param {int} dir
|
||||||
|
*/
|
||||||
|
function indent(first, len, dir) {
|
||||||
|
let selection = idList.slice(first, first + len)
|
||||||
|
|
||||||
|
let result = _.reduce(selection, function (list, itemId) {
|
||||||
|
let item = values[itemId]
|
||||||
|
if (first === 0 && list.length === 0) {
|
||||||
|
values[itemId].indented = 0
|
||||||
|
} else {
|
||||||
|
let indent = item.indented + dir
|
||||||
|
let prevIndent =
|
||||||
|
list.length === 0
|
||||||
|
? values[idList[first - 1]].indented
|
||||||
|
: _.last(list).indented
|
||||||
|
|
||||||
|
indent = Math.max(0, indent)
|
||||||
|
|
||||||
|
if (list.length === 0) {
|
||||||
|
values[idList[first - 1]].fold = 'open'
|
||||||
|
} else {
|
||||||
|
_.last(list).fold = 'open'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indent < prevIndent || Math.abs(prevIndent - indent) <= 1) {
|
||||||
|
values[itemId].indented = indent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.push(item)
|
||||||
|
return list
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} from
|
||||||
|
* @param {string} to
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function moveBefore(from, to) {
|
||||||
|
let fromIndex = _.findIndex(idList, (id) => id === from)
|
||||||
|
let item = values[from]
|
||||||
|
remove(fromIndex, 1)
|
||||||
|
return insertBefore(to, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Number} first
|
||||||
|
* @param {Number} indent
|
||||||
|
* @returns {Number}
|
||||||
|
*/
|
||||||
|
function firstSameIndented(first, indent) {
|
||||||
|
if (first <= 0 || first >= idList.length) return first
|
||||||
|
first--
|
||||||
|
while (first > 0 && values[idList[first]].indented > indent) {
|
||||||
|
first--
|
||||||
|
}
|
||||||
|
return first
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {int} first
|
||||||
|
* @returns {int}
|
||||||
|
*/
|
||||||
|
function lastHigherIndented(first) {
|
||||||
|
if (first < 0 || first >= idList.length) return first
|
||||||
|
let minLevel = values[idList[first]].indented
|
||||||
|
first++;
|
||||||
|
while (first !== idList.length && values[idList[first]].indented > minLevel) {
|
||||||
|
first++
|
||||||
|
}
|
||||||
|
return first
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectItemsFrom(from) {
|
||||||
|
if (!from) {
|
||||||
|
return _.map(idList, id => values[id])
|
||||||
|
}
|
||||||
|
|
||||||
|
let first = _.findIndex(idList, id => id === from)
|
||||||
|
let items = _.map(idList.slice(first + 1), id => values[id])
|
||||||
|
return [values[from], ..._.takeWhile(items, item => item.indented > values[from].indented)]
|
||||||
|
}
|
||||||
|
|
||||||
|
function flat(from) {
|
||||||
|
return selectItemsFrom(from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a tree starting at from node down.
|
||||||
|
* @param from
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function tree(from) {
|
||||||
|
let items = selectItemsFrom(from)
|
||||||
|
|
||||||
|
let top = (stack) => {
|
||||||
|
return stack[stack.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let pop = (stack) => {
|
||||||
|
stack.pop()
|
||||||
|
}
|
||||||
|
let push = (stack, item) => {
|
||||||
|
stack.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack = _.reduce(items, (stack, item) => {
|
||||||
|
if (stack.length === 0) {
|
||||||
|
return [[item]]
|
||||||
|
}
|
||||||
|
|
||||||
|
const stackIndented = top(stack)[0].indented
|
||||||
|
const itemIndented = item.indented
|
||||||
|
|
||||||
|
if (itemIndented > stackIndented) {
|
||||||
|
push(stack, [Object.assign({}, item)])
|
||||||
|
} else {
|
||||||
|
while (stack.length > 1 && itemIndented < stackIndented) {
|
||||||
|
let children = top(stack)
|
||||||
|
pop(stack)
|
||||||
|
let cur = top(stack)
|
||||||
|
cur[cur.length - 1].children = children
|
||||||
|
}
|
||||||
|
top(stack).push(Object.assign({}, item))
|
||||||
|
}
|
||||||
|
return stack
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
while (stack.length > 1) {
|
||||||
|
let children = top(stack)
|
||||||
|
pop(stack)
|
||||||
|
let item = top(stack)
|
||||||
|
item[item.length - 1].children = children
|
||||||
|
}
|
||||||
|
|
||||||
|
return top(stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
_.each(inputData, (d) => {
|
||||||
|
append(d)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentID,
|
||||||
|
index,
|
||||||
|
value,
|
||||||
|
afterValue,
|
||||||
|
insertBefore,
|
||||||
|
insertAfter,
|
||||||
|
indent,
|
||||||
|
append,
|
||||||
|
update,
|
||||||
|
length,
|
||||||
|
slice,
|
||||||
|
remove,
|
||||||
|
moveBefore,
|
||||||
|
firstSameIndented,
|
||||||
|
lastHigherIndented,
|
||||||
|
prevCursorPosition,
|
||||||
|
nextCursorPosition,
|
||||||
|
debug,
|
||||||
|
tree,
|
||||||
|
flat
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Store;
|
51
list-editor/textarea.autosize.js
Normal file
51
list-editor/textarea.autosize.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
function textareaAutosizeIinit($) {
|
||||||
|
|
||||||
|
var pluginName = "textareaAutoSize";
|
||||||
|
var pluginDataName = "plugin_" + pluginName;
|
||||||
|
|
||||||
|
var containsText = function (value) {
|
||||||
|
return (value.replace(/\s/g, '').length > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Plugin(element, options) {
|
||||||
|
this.element = element;
|
||||||
|
this.$element = $(element);
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin.prototype = {
|
||||||
|
init: function () {
|
||||||
|
var diff = parseInt(this.$element.css('paddingBottom')) +
|
||||||
|
parseInt(this.$element.css('paddingTop')) +
|
||||||
|
parseInt(this.$element.css('borderTopWidth')) +
|
||||||
|
parseInt(this.$element.css('borderBottomWidth')) || 0;
|
||||||
|
|
||||||
|
if (containsText(this.element.value)) {
|
||||||
|
this.$element.height(this.element.scrollHeight - diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyup is required for IE to properly reset height when deleting text
|
||||||
|
this.$element.on('input keyup', function (event) {
|
||||||
|
var $window = $(window);
|
||||||
|
var currentScrollPosition = $window.scrollTop();
|
||||||
|
|
||||||
|
$(this)
|
||||||
|
.height(0)
|
||||||
|
.height(this.scrollHeight - diff);
|
||||||
|
|
||||||
|
$window.scrollTop(currentScrollPosition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn[pluginName] = function (options) {
|
||||||
|
this.each(function () {
|
||||||
|
if (!$.data(this, pluginDataName)) {
|
||||||
|
$.data(this, pluginDataName, new Plugin(this, options));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default textareaAutosizeIinit;
|
Loading…
Reference in New Issue
Block a user