{"id":328657,"date":"2022-01-27T09:00:41","date_gmt":"2022-01-27T09:00:41","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=328657"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=328657","title":{"rendered":"<span>React: Code Editor<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/pu\/no\/zh\/punozh3zkth3cqyppf_przd5wco.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u043c \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0435 \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u0432\u0430\u043c, \u043a\u0430\u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u043a\u043e\u0434\u0430 \u043d\u0430 <a href=\"https:\/\/ru.reactjs.org\/\"><code>React<\/code><\/a>.<\/p>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d, \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043d\u0430 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0445 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432, \u0445\u043e\u0442\u044f, \u0441\u043c\u0435\u044e \u043d\u0430\u0434\u0435\u044f\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u0438 \u043e\u043f\u044b\u0442\u043d\u044b\u0435 \u043d\u0430\u0439\u0434\u0443\u0442 \u0432 \u043d\u0435\u043c \u0447\u0442\u043e-\u043d\u0438\u0431\u0443\u0434\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435 \u0434\u043b\u044f \u0441\u0435\u0431\u044f.<\/p>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u0438\u043c\u0435\u0435\u0442\u0441\u044f \u0442\u0440\u0438 \u0432\u043a\u043b\u0430\u0434\u043a\u0438: \u0434\u043b\u044f \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f <code>HTML<\/code>, <code>CSS<\/code> \u0438 <code>JavaScript<\/code>, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e;<\/li>\n<li>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u043c\u0435\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435;<\/li>\n<li>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u043c\u0435\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0431\u0440\u043e\u0441\u0430\u0442\u044c (drop) \u0444\u0430\u0439\u043b\u044b, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435;<\/li>\n<li>\u043a\u043e\u0434, \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c, \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u0432 <code>iframe<\/code> \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u043f\u0435\u0441\u043e\u0447\u043d\u0438\u0446\u044b (sandbox) \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 \u043a\u043d\u043e\u043f\u043a\u0438.<\/li>\n<\/ul>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041f\u0435\u0441\u043e\u0447\u043d\u0438\u0446\u0430:<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<div class=\"oembed\">\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61f22ae9ad041b3b9bfdc91f\" data-style=\"\" id=\"61f22ae9ad041b3b9bfdc91f\" width=\"\"><\/div>\n<\/div>\n<\/div><\/div>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/harryheman\/Blog-Posts\/tree\/master\/code-editor\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439<\/a>.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/www.smashingmagazine.com\/2022\/01\/building-web-code-editor\/\">\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0432\u0434\u043e\u0445\u043d\u043e\u0432\u0435\u043d\u0438\u044f<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u044d\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<p>\u0414\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c 2 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438:<\/p>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/codemirror.net\/index.html\">CodeMirror<\/a> \u2014 \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u043d\u0430 <code>JavaScript<\/code> \u0434\u043b\u044f \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430, \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0439 \u0434\u043b\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u043e\u0434\u0430, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0449\u0438\u0439 \u0431\u043e\u043b\u0435\u0435 100 \u044f\u0437\u044b\u043a\u043e\u0432 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0439 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 (addons) \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u043e\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430;<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/react-codemirror2\">react-codemirror2<\/a> \u2014 \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044f \u043d\u0430\u0434 <code>codemirror<\/code> \u0434\u043b\u044f <code>react<\/code>.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/vitejs.dev\/\"><code>Vite<\/code><\/a>.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0441\u0442\u0438\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u2014 <a href=\"https:\/\/sass-lang.com\/\">Sass<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u2014 <a href=\"https:\/\/yarnpkg.com\/\">Yarn<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0448\u0430\u0431\u043b\u043e\u043d \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\"># code-editor - \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 # --template react - \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439 \u0448\u0430\u0431\u043b\u043e\u043d yarn create vite code-editor --template react<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u0443\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c <code>codemirror<\/code> \u0438 <code>react-codemirror2<\/code> \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u0430 \u0442\u0430\u043a\u0436\u0435 <code>sass<\/code> \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">cd code-editor  yarn add codemirror react-codemirror2  yarn -D sass<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>src<\/code> \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">- components   - Button.jsx - \u043a\u043d\u043e\u043f\u043a\u0430   - CodeEditor.jsx - \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u043e\u0434\u0430   - CodeExecutor.jsx - \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0434\u0430   - Tabs.jsx - \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0432\u043a\u043b\u0430\u0434\u043e\u043a   - ThemeSelector.jsx - \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0432\u044b\u0431\u043e\u0440\u0430 \u0442\u0435\u043c\u044b \u0434\u043b\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430 \u043a\u043e\u0434\u0430 - App.jsx - App.scss - main.jsx<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u043a\u043e\u0434\u0430 <code>react-codemirror2<\/code> \u043c\u043e\u0436\u0435\u0442 \u0432\u043e\u0437\u043d\u0438\u043a\u043d\u0443\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0430 <code>ReferenceError: global is not defined<\/code>. \u0414\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u0443\u0441\u0442\u0440\u0430\u043d\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u0431\u0430\u0433, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 <code>index.html<\/code> \u0442\u0430\u043a\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">&lt;script>   window.global = window &lt;\/script><\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0435\u0444\u043e\u043b\u0442\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f <code>drop<\/code> \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0435 &#171;\u0431\u0440\u043e\u0448\u0435\u043d\u043d\u043e\u0433\u043e&#187; \u0444\u0430\u0439\u043b\u0430 \u0432 \u043d\u043e\u0432\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435 (\u0441\u043c. <a href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/HTML_Drag_and_Drop_API\"><code>Drag and Drop API<\/code><\/a>). \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0435\u0433\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432 <code>main.jsx<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">window.ondrop = (e) => {   e.preventDefault() }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0430\u0439\u043c\u0435\u043c\u0441\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u2014 \u043a\u043d\u043e\u043f\u043a\u0438 (<code>components\/Button.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u0430, \u0442\u0435\u043a\u0441\u0442 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043a\u043d\u043e\u043f\u043a\u0438 export const Button = ({ className, title, onClick }) => (   &lt;button className={className} onClick={onClick}>     {title}   &lt;\/button> )<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0443\u043c\u0430\u044e, \u0437\u0434\u0435\u0441\u044c \u0432\u0441\u0435 \u043f\u043e\u043d\u044f\u0442\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u042f\u0437\u044b\u043a, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0439 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u043e\u043c \u043a\u043e\u0434\u0430, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u043f\u043e\u043c <code>mode<\/code> (\u0440\u0435\u0436\u0438\u043c) \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <code>Controlled<\/code> \u0438\u0437 <code>react-codemirror2<\/code>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u043d\u0430\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430. \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u044d\u0442\u0443 \u043b\u043e\u0433\u0438\u043a\u0443 \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 <code>Tabs<\/code> (<code>components\/Tabs.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 import { Button } from '.\/Button'  \/\/ \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0430\u0441\u0441\u0438\u0432 \u0440\u0435\u0436\u0438\u043c\u043e\u0432 (\u043e\u043d\u0438 \u0436\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0442\u0435\u043a\u0441\u0442\u0430\u043c\u0438 \u043a\u043d\u043e\u043f\u043e\u043a) const tabs = ['HTML', 'CSS', 'JS']  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0440\u0435\u0436\u0438\u043c \u0438 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0435\u0433\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 export const Tabs = ({ mode, setMode }) => {   \/\/ TODO }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0440\u0435\u0436\u0438\u043c\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const changeMode = ({ target: { textContent } }) => {   \/\/ \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0440\u0435\u0436\u0438\u043c\u0430 - \u0442\u0435\u043a\u0441\u0442 \u043a\u043d\u043e\u043f\u043a\u0438 \u0432 \u043d\u0438\u0436\u043d\u0435\u043c \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0435   setMode(textContent.toLowerCase()) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">return (   &lt;div className='tabs'>     {tabs.map((m) => (       &lt;Button         key={m}         title={m}         onClick={changeMode}         \/\/ \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0438         className={m.toLowerCase() === mode ? 'current' : ''}       \/>     ))}   &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043c\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u043f\u043e\u043c <code>theme<\/code> \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <code>Controlled<\/code>. \u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0435\u043c\u044b \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0439 <code>CSS-\u0444\u0430\u0439\u043b<\/code> \u0432 \u043a\u043e\u0434 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430. <code>codemirror<\/code> \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043d\u0430\u0431\u043e\u0440 \u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u0442\u0435\u043c, \u0434\u0435\u043c\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c <a href=\"https:\/\/codemirror.net\/demo\/theme.html#default\">\u0437\u0434\u0435\u0441\u044c<\/a>. \u041c\u044b \u0432\u043e\u0437\u044c\u043c\u0435\u043c 3 \u0442\u0435\u043c\u044b: <code>dracula<\/code>, <code>material<\/code> \u0438 <code>mdn-like<\/code>. \u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0442\u0435\u043c\u044b \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 <code>ThemeSelector<\/code> (<code>components\/ThemeSelector.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0442\u0435\u043c\u044b import 'codemirror\/theme\/dracula.css' import 'codemirror\/theme\/material.css' import 'codemirror\/theme\/mdn-like.css'  \/\/ \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0430\u0441\u0441\u0438\u0432 \u0442\u0435\u043c const themes = ['dracula', 'material', 'mdn-like']  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0442\u0435\u043c\u044b export const ThemeSelector = ({ setTheme }) => {   \/\/ TODO }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0442\u0435\u043c\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const selectTheme = ({ target: { value } }) => {   setTheme(value) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">return (   &lt;div className='theme-selector'>     &lt;label htmlFor='theme'>Theme: &lt;\/label>     &lt;select id='theme' name='theme' onChange={selectTheme}>       {themes.map((t) => (         &lt;option key={t} value={t}>           {t}         &lt;\/option>       ))}     &lt;\/select>   &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0439 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u043a\u043e\u0434\u0430 (<code>components\/CodeEditor.jsx<\/code>).<\/p>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0445\u0443\u043a\u0438, \u043e\u0431\u0435\u0440\u0442\u043a\u0443 \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0445\u0443\u043a import { useState } from 'react' \/\/ \u043e\u0431\u0435\u0440\u0442\u043a\u0430 import { Controlled } from 'react-codemirror2' \/\/ \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b import { Button } from '.\/Button' import { ThemeSelector } from '.\/ThemeSelector'<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0435 \u0441\u0442\u0438\u043b\u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430 \u0438 \u0440\u0435\u0436\u0438\u043c\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0441\u0442\u0438\u043b\u0438 import 'codemirror\/lib\/codemirror.css' \/\/ \u0440\u0435\u0436\u0438\u043c\u044b import 'codemirror\/mode\/xml\/xml' import 'codemirror\/mode\/css\/css' import 'codemirror\/mode\/javascript\/javascript'  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0440\u0435\u0436\u0438\u043c, \u043a\u043e\u0434 \u0438 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0435\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f export const CodeEditor = ({ mode, value, setValue }) => {   \/\/ TODO }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0442\u0435\u043c\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u043e\u0439 \u0442\u0435\u043c\u043e\u0439 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f `dracula` const [theme, setTheme] = useState('dracula')<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0434\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442, \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0435\u043c\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 const changeCode = (editor, data, value) => {   setValue(value) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">&lt;div className='code-editor'>     {\/* \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0432\u044b\u0431\u043e\u0440\u0430 \u0442\u0435\u043c\u044b *\/}     &lt;ThemeSelector setTheme={setTheme} \/>     {\/* \u043e\u0431\u0435\u0440\u0442\u043a\u0430 *\/}     &lt;Controlled       value={value}       onBeforeChange={changeCode}       \/\/ \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438       options={{         \/\/ \u0440\u0435\u0436\u0438\u043c (\u0443\u0441\u043b\u043e\u0432\u043d\u043e, \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u044f\u0437\u044b\u043a \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f)         mode,         \/\/ \u0442\u0435\u043c\u0430         theme       }}       onDrop={onDrop}     \/>   &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0435\u0449\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0448 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0431\u044b\u043b \u0431\u043e\u043b\u0435\u0435 user friendly:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">options={{   mode,   theme,   \/\/ new   lint: true,   lineNumbers: true,   lineWrapping: true,   spellcheck: true }}<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><code>lint: true<\/code>: \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u043c <a href=\"https:\/\/question-it.com\/questions\/4847648\/chto-takoe-linting#:~:text=%D0%9B%D0%B8%D0%BD%D1%82%D0%B8%D0%BD%D0%B3%20%2D%20%D1%8D%D1%82%D0%BE%20%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81%20%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B8%20%D0%B8%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE,%D0%BB%D0%B8%D0%BD%D1%82%D0%B8%D0%BD%D0%B3%20(%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D1%83%20%D0%BA%D0%B0%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D0%BA%D0%BE%D0%B4%D0%B0).\">&#171;\u043b\u0438\u043d\u0442\u0438\u043d\u0433&#187;<\/a> \u043a\u043e\u0434\u0430;<\/li>\n<li><code>lineWrapping: true<\/code>: \u043f\u0440\u0438 \u0434\u043e\u0441\u0442\u0438\u0436\u0435\u043d\u0438\u0438 \u043a\u043e\u043d\u0446\u0430 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u043d\u0430 \u043d\u043e\u0432\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 (\u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u0440\u043e\u043a\u0440\u0443\u0442\u043a\u0438);<\/li>\n<li><code>lineNumbers: true<\/code>: \u043d\u0443\u043c\u0435\u0440\u0430\u0446\u0438\u044f \u0441\u0442\u0440\u043e\u043a;<\/li>\n<li><code>spellcheck: true<\/code>: \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u0440\u0430\u0432\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0421 \u043f\u043e\u043b\u043d\u044b\u043c \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u043c\u043e\u0436\u043d\u043e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f <a href=\"https:\/\/codemirror.net\/doc\/manual.html#config\">\u0437\u0434\u0435\u0441\u044c<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0430\u0440\u043e\u0447\u043a\u0443 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432. \u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0438\u0445:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import 'codemirror\/addon\/edit\/closetag' import 'codemirror\/addon\/edit\/closebrackets' import 'codemirror\/addon\/edit\/matchtags' import 'codemirror\/addon\/edit\/matchbrackets'<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">options={{   mode,   theme,   lint: true,   lineNumbers: true,   lineWrapping: true,   spellcheck: true,   \/\/ new   autoCloseTags: true,   autoCloseBrackets: true,   matchTags: true,   matchBrackets: true }}<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><code>autoCloseTags: true<\/code> \u2014 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043f\u0440\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u044e\u0449\u0438\u0445 HTML-\u0442\u0435\u0433\u043e\u0432;<\/li>\n<li><code>autoCloseBrackets: true<\/code> \u2014 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043f\u0440\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u044e\u0449\u0438\u0445 \u0441\u043a\u043e\u0431\u043e\u043a;<\/li>\n<li><code>matchTags: true<\/code> \u2014 \u043f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u0430 \u043f\u0430\u0440\u043d\u044b\u0445 \u0442\u0435\u0433\u043e\u0432;<\/li>\n<li><code>matchBrackets: true<\/code> \u2014 \u043f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u0430 \u043f\u0430\u0440\u043d\u044b\u0445 \u0441\u043a\u043e\u0431\u043e\u043a.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0421 \u043f\u043e\u043b\u043d\u044b\u043c \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u043c\u043e\u0436\u043d\u043e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f <a href=\"https:\/\/codemirror.net\/doc\/manual.html#addons\">\u0437\u0434\u0435\u0441\u044c<\/a>.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u043c\u0441\u044f \u043a \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u043c\u0443 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443 (<code>App.jsx<\/code>).<\/p>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0441\u0442\u0438\u043b\u0438, \u0445\u0443\u043a \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import '.\/App.scss' import { useState } from 'react' import { Tabs } from '.\/components\/Tabs' import { CodeEditor } from '.\/components\/CodeEditor'<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f <code>HTML<\/code>, <code>CSS<\/code> \u0438 <code>JS<\/code> \u0434\u043b\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0441 \u0442\u0435\u043a\u0441\u0442\u043e\u043c `hi` const initialHTML = '&lt;h1>hi&lt;\/h1>' \/\/ \u0437\u0435\u043b\u0435\u043d\u043e\u0433\u043e \u0446\u0432\u0435\u0442\u0430 const initialCSS = ` h1 {   color: green; } ` \/\/ \u043f\u0440\u0438 \u043a\u043b\u0438\u043a\u0435 \u0442\u0435\u043a\u0441\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 `bye`, \/\/ \u0430 \u0446\u0432\u0435\u0442 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043a\u0440\u0430\u0441\u043d\u044b\u043c \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u043c const initialJavaScript = ` document.querySelector(\"h1\").addEventListener('click', function () {   this.textContent = \"bye\"   this.style.color = \"red\" }, { once: true }) `  export default function App() {   \/\/ TODO }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0440\u0435\u0436\u0438\u043c\u0430, <code>HTML<\/code>, <code>CSS<\/code> \u0438 <code>JS<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0440\u0435\u0436\u0438\u043c\u043e\u043c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f `HTML` const [mode, setMode] = useState('html') const [html, setHtml] = useState(initialHTML) const [css, setCss] = useState(initialCSS.trim()) const [js, setJs] = useState(initialJavaScript.trim())<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0434\u0438\u043d \u043d\u044e\u0430\u043d\u0441: \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0449\u0438\u0435 \u043d\u0430\u0441 \u0440\u0435\u0436\u0438\u043c\u044b \u043d\u043e\u0441\u044f\u0442 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f <code>xml<\/code>, <code>css<\/code> \u0438 <code>javascript<\/code> \u0432 <code>codemirror<\/code>, \u043e\u0434\u043d\u0430\u043a\u043e \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u0445 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u0440\u0443\u0433\u0438\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u2014 <code>html<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>xml<\/code> \u0438 <code>js<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>javascript<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0441 \u043f\u0440\u043e\u043f\u0430\u043c\u0438 \u0434\u043b\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const propsByMode = {   html: {     mode: 'xml',     value: html,     setValue: setHtml   },   css: {     mode: 'css',     value: css,     setValue: setCss   },   js: {     mode: 'javascript',     value: js,     setValue: setJs   } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043b\u044e\u0447\u0430\u043c\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f &#171;\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435&#187; \u0440\u0435\u0436\u0438\u043c\u044b.<\/p>\n<p>  <\/p>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">return (   &lt;div className='app'>     &lt;h1>React Code Editor&lt;\/h1>     &lt;Tabs mode={mode} setMode={setMode} \/>     {\/* \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 *\/}     &lt;CodeEditor {...propsByMode[mode]} \/>   &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u0421\u0442\u0438\u043b\u0438:<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"plaintext\">$primary: #0275d8; $success: #5cb85c; $warning: #f0ad4e; $danger: #d9534f; $light: #f7f7f7; $dark: #292b2c;  @mixin reset($font-family, $font-size, $color) {   margin: 0;   padding: 0;   box-sizing: border-box;   @if $font-family {     font-family: $font-family;   }   @if $font-size {     font-size: $font-size;   }   @if $color {     color: $color;   } }  @mixin flex-center($column: false) {   display: flex;   justify-content: center;   align-items: center;    @if $column {     &amp; {       flex-direction: column;     }   } }  @import url('https:\/\/fonts.googleapis.com\/css2?family=Montserrat:wght@200;400;600&amp;display=swap');  *:not(.react-codemirror2 *) {   @include reset('Montserrat', 1rem, $dark); }  .app {   @include flex-center(true);   margin: 0 auto;   max-width: 600px;    h1 {     margin: 1rem 0;     font-size: 1.6rem;     text-align: center;   }    .tabs {     button {       margin: 0.5rem;       padding: 0.5rem;       background: none;       border: none;       outline: none;       border-radius: 6px;       transition: 0.4s;       cursor: pointer;       user-select: none;        &amp;:hover,       &amp;.current {         background: $dark;         color: $light;       }     }   }    .theme-selector {     margin: 0.75rem 0;     text-align: center;      select {       border-radius: 4px;     }   }    .code-editor {     width: 100%;      .CodeMirror-wrap {       border-radius: 4px;       padding: 0.25rem;     }   }    .code-executor {     width: 100%;   }    .btn {     margin: 0.75rem;     padding: 0.25rem 0.75rem;     background: $primary;     border: none;     border-radius: 4px;     outline: none;     color: $light;     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);     cursor: pointer;     user-select: none;     transition: 0.3s;      &amp;:active {       box-shadow: none;     }      &amp;.run {       background: $success;     }   }    iframe {     padding: 0.25rem;     width: 100%;     border: 1px dashed $dark;     border-radius: 4px;   } }<\/code><\/pre>\n<\/div><\/div>\n<p>  <\/p>\n<p>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 <code>yarn dev<\/code> \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <code>http:\/\/localhost:3000<\/code>:<\/p>\n<p>  <img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/s9\/om\/on\/s9omonuayja8xc9_askewtcgu5y.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0448 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0438\u0440\u0443\u0435\u0442: \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043c\u0435\u0436\u0434\u0443 \u0432\u043a\u043b\u0430\u0434\u043a\u0430\u043c\u0438, \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u0438 \u043c\u0435\u043d\u044f\u0442\u044c \u0442\u0435\u043c\u044b.<\/p>\n<p>  <img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/kv\/zu\/52\/kvzu52kui2ymhepel4i1oohntsy.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041d\u043e \u0447\u0442\u043e \u0442\u043e\u043b\u043a\u0443 \u043e\u0442 \u043a\u043e\u0434\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435\u043b\u044c\u0437\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c?<\/p>\n<p>  <\/p>\n<p>\u0412 \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f <code>HTML+CSS+JS<\/code> \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTML\/Element\/iframe\"><code>iframe<\/code><\/a>. \u041d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 2 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 \u044d\u0442\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>srcdoc<\/code>: \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0432 <code>iframe<\/code> \u0438\u043d\u043b\u0430\u0439\u043d\u043e\u0432\u044b\u0439 <code>HTML<\/code>;<\/li>\n<li><code>sandbox<\/code>: \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043d\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0439 \u0432 <code>iframe<\/code> \u043a\u043e\u043d\u0442\u0435\u043d\u0442.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0434\u0430 (<code>components\/CodeExecutor.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Button } from '.\/Button'  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 `srcdoc` \u0438 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f export const CodeExecutor = ({ srcDoc, runCode }) => (   &lt;div className='code-executor'>     &lt;Button className='btn run' title='Run code' onClick={runCode} \/>     &lt;iframe       srcDoc={srcDoc}       title='output'       \/\/ \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0432       sandbox='allow-scripts'     \/>   &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u0432 <code>App.jsx<\/code> \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c <code>CodeExecutor<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { CodeExecutor } from '.\/components\/CodeExecutor'<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 <code>srcdoc<\/code> \u0438 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0435\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const [srcDoc, setSrcDoc] = useState('')  const runCode = () => {   setSrcDoc(     `&lt;html>       &lt;style>${css}&lt;\/style>       &lt;body>${html}&lt;\/body>       &lt;script>${js}&lt;\/script>     &lt;\/html>`   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043f\u0440\u043e\u043f\u044b \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443 <code>CodeExecutor<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">&lt;CodeExecutor srcDoc={srcDoc} runCode={runCode} \/><\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440:<\/p>\n<p>  <img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/xu\/jb\/si\/xujbsipg_lsistbbjy5oqahuuz4.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443 <code>Run code<\/code>:<\/p>\n<p>  <img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/qf\/fb\/p4\/qffbp4zzwakyyz8wlr8i4oy8hym.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0412\u0438\u0434\u0438\u043c, \u0447\u0442\u043e \u043d\u0430\u0448 \u043a\u043e\u0434 \u0431\u043b\u0430\u0433\u043e\u043f\u043e\u043b\u0443\u0447\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f. \u041a\u0440\u0443\u0442\u043e!<\/p>\n<p>  <\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0438 \u0431\u0440\u043e\u0441\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432.<\/p>\n<p>  <\/p>\n<p>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u043a\u0440\u044b\u0442\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430 \u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 <code>CodeEditor<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0438\u043c\u043c\u0443\u0442\u0430\u0431\u0435\u043b\u044c\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0438\u043d\u043f\u0443\u0442:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const fileInputRef = useRef()<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0441\u0442\u0438 \u0444\u0430\u0439\u043b\u0430, \u0442.\u0435. \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u043c\u0443 \u0440\u0435\u0436\u0438\u043c\u0443-\u0432\u043a\u043b\u0430\u0434\u043a\u0435:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const isFileValid = (file) =>   (mode === 'xml' &amp;&amp; file.type === 'text\/html') || file.type.includes(mode)<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 \u043a\u0430\u043a \u0442\u0435\u043a\u0441\u0442\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/developer.mozilla.org\/ru\/docs\/Web\/API\/FileReader\"><code>FileReader<\/code><\/a>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const readFile = (file) => {   if (!isFileValid(file)) return    \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 `FileReader`   const reader = new FileReader()    \/\/ \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0447\u0442\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430   reader.onloadend = () => {     \/\/ \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u0434\u0430     setValue(reader.result)   }    \/\/ \u0447\u0438\u0442\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u043a\u0430\u043a \u0442\u0435\u043a\u0441\u0442   reader.readAsText(file) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const loadFile = (e) => {   const file = e.target.files[0]    readFile(file) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443 \u043a\u043d\u043e\u043f\u043a\u0443 \u0438 \u0438\u043d\u043f\u0443\u0442:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">&lt;Button   className='btn file'   title='Load file'   onClick={() => {     \/\/ \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u043a\u043b\u0438\u043a \u0441\u043a\u0440\u044b\u0442\u043e\u043c\u0443 \u0438\u043d\u043f\u0443\u0442\u0443     fileInputRef.current.click()   }} \/> &lt;input   type='file'   accept='text\/html, text\/css, text\/javascript'   style={{ display: 'none' }}   aria-hidden='true'   ref={fileInputRef}   \/\/ \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0438 \u0447\u0442\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430   onChange={loadFile} \/><\/code><\/pre>\n<p>  <\/p>\n<p>\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0431\u0440\u043e\u0441\u0430\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\"> const onDrop = (editor, e) => {   e.preventDefault()    const file = e.dataTransfer.items[0].getAsFile()    readFile(file) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0435\u0435 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0433\u043e \u043f\u0440\u043e\u043f\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443 <code>Controlled<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">&lt;Controlled   onDrop={onDrop}   \/\/ \u0434\u0440\u0443\u0433\u0438\u0435 \u043f\u0440\u043e\u043f\u044b \/><\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 `CodeEditor`:<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import { useState, useRef } from 'react' import { Controlled } from 'react-codemirror2' import { Button } from '.\/Button' import { ThemeSelector } from '.\/ThemeSelector'  import 'codemirror\/lib\/codemirror.css'  import 'codemirror\/mode\/xml\/xml' import 'codemirror\/mode\/css\/css' import 'codemirror\/mode\/javascript\/javascript'  import 'codemirror\/addon\/edit\/closetag' import 'codemirror\/addon\/edit\/closebrackets' import 'codemirror\/addon\/edit\/matchtags' import 'codemirror\/addon\/edit\/matchbrackets'  export const CodeEditor = ({ mode, value, setValue }) => {   const [theme, setTheme] = useState('dracula')   const fileInputRef = useRef()    const changeCode = (editor, data, value) => {     setValue(value)   }    const isFileValid = (file) =>     (mode === 'xml' &amp;&amp; file.type === 'text\/html') || file.type.includes(mode)    const readFile = (file) => {     if (!isFileValid(file)) return      const reader = new FileReader()      reader.onloadend = () => {       setValue(reader.result)     }      reader.readAsText(file)   }    const loadFile = (e) => {     const file = e.target.files[0]      readFile(file)   }    const onDrop = (editor, e) => {     e.preventDefault()      const file = e.dataTransfer.items[0].getAsFile()      readFile(file)   }    return (     &lt;div className='code-editor'>       &lt;ThemeSelector setTheme={setTheme} \/>       &lt;Button         className='btn file'         title='Load file'         onClick={() => {           fileInputRef.current.click()         }}       \/>       &lt;input         type='file'         accept='text\/html, text\/css, text\/javascript'         style={{ display: 'none' }}         aria-hidden='true'         ref={fileInputRef}         onChange={loadFile}       \/>       &lt;Controlled         value={value}         onBeforeChange={changeCode}         onDrop={onDrop}         options={{           mode,           theme,           lint: true,           lineNumbers: true,           lineWrapping: true,           spellcheck: true,           autoCloseTags: true,           autoCloseBrackets: true,           matchTags: true,           matchBrackets: true         }}       \/>     &lt;\/div>   ) }<\/code><\/pre>\n<\/div><\/div>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u0438\u043c\u0435\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443, \u0441\u0442\u0438\u043b\u0438 \u0438 \u0441\u043a\u0440\u0438\u043f\u0442\u044b \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u043d\u043e \u0442\u0430\u043a\u0436\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0438 \u0431\u0440\u043e\u0441\u0430\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0444\u0430\u0439\u043b\u044b:<\/p>\n<p>  <img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/7n\/gn\/nn\/7ngnnnrzlyt9wnwzlwxldxczuys.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0436\u0430\u043b\u0443\u0439, \u044d\u0442\u043e \u0432\u0441\u0435, \u0447\u0435\u043c \u044f \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441 \u0432\u0430\u043c\u0438 \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0432\u0430\u043c \u0431\u044b\u043b\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0438 \u0432\u044b \u043d\u0435 \u0436\u0430\u043b\u0435\u0435\u0442\u0435 \u043f\u043e\u0442\u0440\u0430\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<\/p>\n<p>  <\/p>\n<p>\u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0438 happy coding!<\/p>\n<p>  <\/p>\n<hr\/>\n<p>  <\/p>\n<p><a href=\"https:\/\/cloud.timeweb.com\/?utm_source=habr&amp;utm_medium=banner&amp;utm_campaign=cloud&amp;utm_content=direct&amp;utm_term=low\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/wn\/cq\/lp\/wncqlp9abeml4npwzsybuvhzcta.png\"\/><\/a><\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/company\/timeweb\/blog\/648041\/\"> https:\/\/habr.com\/ru\/company\/timeweb\/blog\/648041\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" data-src=\"https:\/\/habrastorage.org\/webt\/pu\/no\/zh\/punozh3zkth3cqyppf_przd5wco.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u043c \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0435 \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u0432\u0430\u043c, \u043a\u0430\u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u043a\u043e\u0434\u0430 \u043d\u0430 <a href=\"https:\/\/ru.reactjs.org\/\"><code>React<\/code><\/a>.<\/p>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d, \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043d\u0430 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0445 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432, \u0445\u043e\u0442\u044f, \u0441\u043c\u0435\u044e \u043d\u0430\u0434\u0435\u044f\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u0438 \u043e\u043f\u044b\u0442\u043d\u044b\u0435 \u043d\u0430\u0439\u0434\u0443\u0442 \u0432 \u043d\u0435\u043c \u0447\u0442\u043e-\u043d\u0438\u0431\u0443\u0434\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435 \u0434\u043b\u044f \u0441\u0435\u0431\u044f.<\/p>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u0438\u043c\u0435\u0435\u0442\u0441\u044f \u0442\u0440\u0438 \u0432\u043a\u043b\u0430\u0434\u043a\u0438: \u0434\u043b\u044f \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f <code>HTML<\/code>, <code>CSS<\/code> \u0438 <code>JavaScript<\/code>, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e;<\/li>\n<li>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u043c\u0435\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435;<\/li>\n<li>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u043c\u0435\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0431\u0440\u043e\u0441\u0430\u0442\u044c (drop) \u0444\u0430\u0439\u043b\u044b, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435;<\/li>\n<li>\u043a\u043e\u0434, \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c, \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u0432 <code>iframe<\/code> \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u043f\u0435\u0441\u043e\u0447\u043d\u0438\u0446\u044b (sandbox) \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 \u043a\u043d\u043e\u043f\u043a\u0438.<\/li>\n<\/ul>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041f\u0435\u0441\u043e\u0447\u043d\u0438\u0446\u0430:<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<div class=\"oembed\">\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61f22ae9ad041b3b9bfdc91f\" data-style=\"\" id=\"61f22ae9ad041b3b9bfdc91f\" width=\"\"><\/div>\n<\/div>\n<\/div><\/div>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/harryheman\/Blog-Posts\/tree\/master\/code-editor\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439<\/a>.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/www.smashingmagazine.com\/2022\/01\/building-web-code-editor\/\">\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0432\u0434\u043e\u0445\u043d\u043e\u0432\u0435\u043d\u0438\u044f<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u044d\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-328657","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/328657","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=328657"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/328657\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=328657"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=328657"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=328657"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}