feeles-ide
Version:
The hackable and serializable IDE to make learning material
634 lines (505 loc) • 20.6 kB
JavaScript
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf3 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _jshint = require("jshint");
var _deepEqual = _interopRequireDefault(require("deep-equal"));
var _reduce = _interopRequireDefault(require("lodash/reduce"));
var _includes = _interopRequireDefault(require("lodash/includes"));
var _codemirror = _interopRequireDefault(require("codemirror"));
require("codemirror/mode/meta");
require("codemirror/mode/htmlmixed/htmlmixed");
require("codemirror/mode/css/css");
require("codemirror/mode/javascript/javascript");
require("codemirror/mode/yaml/yaml");
require("codemirror/mode/markdown/markdown");
require("codemirror/addon/hint/show-hint");
require("codemirror/addon/hint/html-hint");
require("codemirror/addon/hint/css-hint");
require("codemirror/addon/edit/closebrackets");
require("codemirror/addon/edit/matchbrackets");
require("codemirror/addon/comment/comment");
require("codemirror/addon/dialog/dialog");
require("codemirror/addon/search/search");
require("codemirror/addon/search/searchcursor");
require("codemirror/addon/fold/foldcode");
require("codemirror/addon/fold/foldgutter");
require("codemirror/addon/fold/brace-fold");
require("codemirror/keymap/sublime");
require("codemirror/lib/codemirror.css");
require("codemirror/addon/hint/show-hint.css");
require("codemirror/addon/dialog/dialog.css");
require("codemirror/addon/fold/foldgutter.css");
var _glsl = _interopRequireDefault(require("glsl-editor/glsl"));
require("./codemirror-hint-extension");
var _CodeMirrorComponent = _interopRequireDefault(require("../../utils/CodeMirrorComponent"));
// import 'codemirror/addon/scroll/simplescrollbars';
// import 'codemirror/addon/scroll/simplescrollbars.css';
(0, _glsl.default)(_codemirror.default);
_codemirror.default.modeInfo.push({
name: 'glsl',
mime: 'text/x-glsl',
mode: 'glsl'
}); // YAML のエイリアス (.yml) (text/yaml)
_codemirror.default.modeInfo.push({
name: 'YAML',
mimes: ['text/yaml', 'text/x-yaml'],
mode: 'yaml',
ext: ['yml', 'yaml'],
alias: ['yml']
}); // segments の参照と現在の line との関係を一旦保持するマップ
var segmentsLineMap = new WeakMap();
var Editor =
/*#__PURE__*/
function (_Component) {
(0, _inherits2.default)(Editor, _Component);
function Editor() {
var _getPrototypeOf2;
var _this;
(0, _classCallCheck2.default)(this, Editor);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = (0, _possibleConstructorReturn2.default)(this, (_getPrototypeOf2 = (0, _getPrototypeOf3.default)(Editor)).call.apply(_getPrototypeOf2, [this].concat(args)));
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "state", {
jshintrc: null,
dropdowns: [],
dropdownLineWidgets: [],
links: [],
linkLineWidgets: []
});
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "handleCodemirror", function (ref) {
if (!ref) return;
var cm = ref.getCodeMirror();
if (cm) {
_this.showHint(cm);
_this.props.codemirrorRef(cm); // ドロップダウンウィジェット
cm.on('changes', _this.handleUpdateDropdown);
cm.on('changes', _this.handleUpdateLink);
cm.on('swapDoc', function () {
_this.clearAllWidgets();
_this.handleUpdateDropdown(cm, []);
_this.handleUpdateLink(cm, []);
});
_this.handleUpdateDropdown(cm, []);
_this.handleUpdateLink(cm, []);
cm.on('unfold', function (cm) {
_this.clearAllWidgets();
_this.handleUpdateDropdown(cm, []);
_this.handleUpdateLink(cm, []);
});
}
});
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "handleValueClick", function (event) {
// Put cursor into editor
if (_this.codemirror) {
var locate = {
left: event.x,
top: event.y
};
var pos = _this.codemirror.coordsChar(locate);
_this.codemirror.focus();
_this.codemirror.setCursor(pos);
}
});
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "handleUpdateDropdown", function (cm, batch) {
var origin = batch[0] && batch[0].origin; // e.g. '+input'
var dropdowns = (0, _reduce.default)(cm.getValue('\n').split('\n'), function (prev, text, line) {
// Syntax: ('▼ スキン', _kきし)
var segments = /^(.*\(['"])(▼[^'"]*)(['"],\s*)([^)]*)\)/.exec(text);
if (segments) {
// line は独立して保持(異なる行でも移動しただけなら問題ない)
segmentsLineMap.set(segments, line); // 新しいデータを格納
prev = prev.concat([segments]);
}
return prev;
}, []); // 中身が変わっていたら更新
if ((0, _includes.default)(['setValue', 'undo'], origin) || !(0, _deepEqual.default)(dropdowns, _this.state.dropdowns)) {
// 前回の LineWidget を消去
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = _this.state.dropdownLineWidgets[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var item = _step.value;
item.clear(); // remove element
} // 今回の LineWidget を追加
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var dropdownLineWidgets = dropdowns.map(function (segments) {
var line = segmentsLineMap.get(segments); // さっき保持した line
return _this.renderDropdown(cm, segments, line);
});
_this.setState({
dropdowns: dropdowns,
dropdownLineWidgets: dropdownLineWidgets
});
}
});
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "clearAllWidgets", function () {
// 全ての LineWidget を消去
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = _this.state.dropdownLineWidgets[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var item = _step2.value;
item.clear(); // remove element
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = _this.state.linkLineWidgets[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var _item = _step3.value;
_item.clear(); // remove element
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
_this.setState({
dropdowns: [],
dropdownLineWidgets: [],
links: [],
linkLineWidgets: []
});
});
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "renderDropdown", function (cm, segments, line) {
var _segments = (0, _slicedToArray2.default)(segments, 5),
_prefix = _segments[1],
_label = _segments[2],
_right = _segments[3],
_value = _segments[4];
var label = document.createElement('span');
label.textContent = _label;
label.classList.add('Feeles-dropdown-label');
var right = document.createElement('span');
right.textContent = _right;
right.classList.add('Feeles-dropdown-blank');
var value = document.createElement('span');
value.textContent = _value;
value.classList.add('Feeles-dropdown-value');
value.addEventListener('click', _this.handleValueClick);
var button = document.createElement('span');
button.appendChild(label); // "▼ スキン"
button.appendChild(right); // "', "
button.appendChild(value); // _kきし
button.classList.add('Feeles-dropdown-button');
var allOfLeft = _prefix + _label + _right; // value より左の全て
var ch = allOfLeft.length;
var shadow = document.createElement('span');
shadow.appendChild(button);
shadow.classList.add('Feeles-dropdown-shadow');
var parent = document.createElement('div');
parent.classList.add('Feeles-widget', 'Feeles-dropdown');
parent.appendChild(shadow);
var pos = {
line: line,
ch: _prefix.length
};
var _cm$charCoords = cm.charCoords(pos, 'local'),
left = _cm$charCoords.left;
parent.style.transform = "translate(".concat(left, "px, -1.3rem)"); // ウィジェット追加
var widget = cm.addLineWidget(line, parent, {
insertAt: 0
}); // クリックイベント追加
button.addEventListener('click', function () {
var dropdownConfig = _this.state.dropdownConfig;
if (!dropdownConfig) return;
var line = cm.doc.getLineNumber(widget.line); // Open dropdown menu
var listName = _label.substr(1).trim();
var list = dropdownConfig[listName];
if (list) {
var _hint = {
from: {
line: line,
ch: ch
},
to: {
line: line,
ch: ch + _value.length
},
list: list.map(function (item) {
return {
text: item.body,
displayText: "".concat(item.body, " ").concat(item.label || '')
};
}) // CodeMirror.on(hint, 'pick', this.handleSaveAndRun);
};
cm.showHint({
completeSingle: false,
hint: function hint() {
return _hint;
}
});
cm.focus();
}
}, true);
return widget;
});
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "handleUpdateLink", function (cm, batch) {
var origin = batch[0] && batch[0].origin; // e.g. '+input'
var eachLines = cm.getValue('\n').split('\n');
var links = [];
eachLines.map(function (currentLine, line) {
// Syntax: // [で囲むと[リンク]になります
// Syntax: // `[これ]`はリンクにはなりません. `[これ]はなります
var commentStart = currentLine.indexOf('//');
var outerPrefix = currentLine.substr(0, commentStart);
var commentLine = currentLine.substr(commentStart);
if (commentStart > -1) {
commentLine.split('`').map(function (text, index, array) {
var innerPrefix = '';
if (index % 2 === 0 || index === array.length - 1) {
var isFirst = true;
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
try {
for (var _iterator4 = text.split('[')[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
var linkAndMore = _step4.value;
if (!isFirst && (0, _includes.default)(linkAndMore, ']')) {
var linkText = linkAndMore.split(']')[0];
var segments = {
prefix: outerPrefix + innerPrefix,
linkText: linkText // line は独立して保持(異なる行でも移動しただけなら問題ない)
};
segmentsLineMap.set(segments, line); // 新しいデータを格納
links.push(segments);
}
isFirst = false;
innerPrefix += "".concat(linkAndMore, "[");
}
} catch (err) {
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion4 && _iterator4.return != null) {
_iterator4.return();
}
} finally {
if (_didIteratorError4) {
throw _iteratorError4;
}
}
}
}
outerPrefix += text + '`';
});
}
}); // 中身が変わっていたら更新
if ((0, _includes.default)(['setValue', 'undo'], origin) || !(0, _deepEqual.default)(links, _this.state.links)) {
// 前回の LineWidget を消去
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
try {
for (var _iterator5 = _this.state.linkLineWidgets[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
var item = _step5.value;
item.clear(); // remove element
} // 今回の LineWidget を追加
} catch (err) {
_didIteratorError5 = true;
_iteratorError5 = err;
} finally {
try {
if (!_iteratorNormalCompletion5 && _iterator5.return != null) {
_iterator5.return();
}
} finally {
if (_didIteratorError5) {
throw _iteratorError5;
}
}
}
var linkLineWidgets = links.map(function (segments) {
var line = segmentsLineMap.get(segments); // さっき保持した line
return _this.renderlink(cm, segments, line);
});
_this.setState({
links: links,
linkLineWidgets: linkLineWidgets
}); // ShotCard に Scrapbox 風のリンクを表示するために
// link object を ShotCard に渡す
if (_this.props.handleSetLinkObjects) {
_this.props.handleSetLinkObjects(links);
}
}
});
return _this;
}
(0, _createClass2.default)(Editor, [{
key: "shouldComponentUpdate",
value: function shouldComponentUpdate(nextProps) {
if (this.props.file === nextProps.file) {
return false;
}
return true;
}
}, {
key: "componentDidMount",
value: function componentDidMount() {
var jshintrc = this.props.findFile('.jshintrc');
if (jshintrc) {
// .jshintrc があれば JSHint でチェック
window.JSHINT = _jshint.JSHINT;
try {
this.setState({
jshintrc: JSON.parse(jshintrc.text)
});
} catch (e) {// continue regardless of error
}
}
this.setState({
dropdownConfig: this.props.loadConfig('dropdown')
});
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps) {
if (prevProps.fileView !== this.props.fileView) {
this.setState({
dropdownConfig: this.props.loadConfig('dropdown')
});
}
}
}, {
key: "renderlink",
value: function renderlink(cm, segments, line) {
var prefix = segments.prefix,
linkText = segments.linkText;
var linkElement = document.createElement('a');
linkElement.href = "https://scrapbox.io/hackforplay/".concat(encodeURIComponent(linkText));
linkElement.target = '_blank';
linkElement.textContent = linkText;
linkElement.classList.add('Feeles-link');
var pos = {
line: line,
ch: prefix.length
};
var _cm$charCoords2 = cm.charCoords(pos, 'local'),
left = _cm$charCoords2.left;
linkElement.style.transform = "translate(".concat(left, "px, -1.2rem)"); // ウィジェット追加
var widget = cm.addLineWidget(line, linkElement);
return widget;
}
}, {
key: "showHint",
value: function showHint(cm) {
var _this2 = this;
if (!this.props.showHint) {
return;
}
var getFiles = this.props.getFiles;
cm.on('change', function (_cm, change) {
if (change.origin === 'setValue' || change.origin === 'complete') return;
var cursor = cm.getCursor();
cm.scrollIntoView(cursor);
cm.showHint({
completeSingle: false,
files: getFiles(),
snippets: _this2.props.snippets
});
});
}
}, {
key: "render",
value: function render() {
var _this$props = this.props,
file = _this$props.file,
lineNumbers = _this$props.lineNumbers;
var meta = _codemirror.default.findModeByMIME(file.type);
var mode = meta && meta.mode;
return _react.default.createElement(_CodeMirrorComponent.default, {
id: file.key,
value: file.text,
mode: mode,
lineNumbers: lineNumbers,
keyMap: "sublime",
foldGutter: true,
foldOptions: this.props.foldOptions,
extraKeys: this.props.extraKeys,
ref: this.handleCodemirror
});
}
}]);
return Editor;
}(_react.Component);
exports.default = Editor;
(0, _defineProperty2.default)(Editor, "propTypes", {
file: _propTypes.default.object.isRequired,
getFiles: _propTypes.default.func.isRequired,
getConfig: _propTypes.default.func.isRequired,
codemirrorRef: _propTypes.default.func.isRequired,
snippets: _propTypes.default.array.isRequired,
showHint: _propTypes.default.bool.isRequired,
extraKeys: _propTypes.default.object.isRequired,
foldOptions: _propTypes.default.object,
lineNumbers: _propTypes.default.bool.isRequired,
findFile: _propTypes.default.func.isRequired,
loadConfig: _propTypes.default.func.isRequired,
fileView: _propTypes.default.object.isRequired,
handleSetLinkObjects: _propTypes.default.func
});
(0, _defineProperty2.default)(Editor, "defaultProps", {
getFiles: function getFiles() {
return [];
},
codemirrorRef: function codemirrorRef() {},
snippets: [],
showHint: true,
extraKeys: {},
lineNumbers: true,
foldOptions: {}
});