UNPKG

feeles-ide

Version:

The hackable and serializable IDE to make learning material

634 lines (505 loc) 20.6 kB
"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: {} });