HTML5 Drag'n'Drop рдПрдкреАрдЖрдИ рдХреЗ рд╕рд╛рде рдЫрдВрдЯрдиреА

Sortable.js рдЖрдзреБрдирд┐рдХ рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рдФрд░ рд╕реНрдкрд░реНрд╢ рдЙрдкрдХрд░рдгреЛрдВ рдХреЗ рд▓рд┐рдП рдПрдХ рдиреНрдпреВрдирддрдо рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╣реИ рдЬрд┐рд╕рдореЗрдВ jQuery рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реЛрддреА рд╣реИред

рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рдирд╛рдо рд╕реЗ рдЕрдиреБрдорд╛рди рд▓рдЧрд╛ рд╕рдХрддреЗ рд╣реИрдВ, рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЛ drag'n'drop рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЖрдЗрдЯрдо рд╕реЙрд░реНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рдРрд╕реЗ рдорд╛рдорд▓реЛрдВ рдореЗрдВ рдорд╛рдирдХ рд╕рдорд╛рдзрд╛рди jQuery UI / Sortable рд╣реИ , рдЬреЛ рди рддреЛ рдЕрдзрд┐рдХ рд╣реИ рдФрд░ рди рд╣реА 64 kb + 10 kb рд╕реЗ рдХрдо рд╣реИред рдХреБрд▓ 75 kb рдПрдХ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ gzipped рд╣реИ рдЬрд╣рд╛рдБ jQuery рдХрд╛ рдЙрдкрдпреЛрдЧ рдмрд┐рд▓реНрдХреБрд▓ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдЕрдкреЗрдХреНрд╖рд╛рдХреГрдд рд╣рд╛рд▓ рд╣реА рдореЗрдВ Habr├й рдкрд░ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдЗрд╕реА рддрд░рд╣ рдХреА рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдПрдХ рд▓реЗрдЦ рдерд╛, рд▓реЗрдХрд┐рди рдлрд┐рд░ рд╕реЗ рдкреНрд░рд╕реНрддрд╛рд╡рд┐рдд рд╕рдорд╛рдзрд╛рди рдореЗрдВ jQuery рдФрд░ рд╕реНрдкрд░реНрд╢ рдЙрдкрдХрд░рдгреЛрдВ рдХрд╛ рд╕рдорд░реНрдерди рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред

рд╡рдЬрди рдХреЗ рд╕рд╛рде рд╕рдорд╕реНрдпрд╛рдУрдВ рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдореБрдЭреЗ рдорд┐рд▓реА рд╕рднреА рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдЧрддрд┐рд╢реАрд▓ рд░реВрдк рд╕реЗ рдмрджрд▓рддреА рд╕реВрдЪреА рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рдирд╣реАрдВ рдереАрдВред рдкреНрд▓рдЧрдЗрди рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬрд╝реЗрд╢рди рдХреЗ рд╕рдордп, рдЙрдиреНрд╣реЛрдВрдиреЗ рд╕рднреА рддрддреНрд╡реЛрдВ рдХреА рд╕реНрдерд┐рддрд┐ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХреА, рдФрд░ рдЙрдиреНрд╣реЗрдВ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдкреНрд▓рдЧрдЗрди рдХреЛ рдлрд┐рд░ рд╕реЗ рд╕рдВрдЧрдард┐рдд рдХрд░рдирд╛ рдпрд╛ $('...').sortable('refresh') рдХреЛ рд╕реЙрд░реНрдЯ рдХрд░рдирд╛ рдЖрд╡рд╢реНрдпрдХ рдерд╛ $('...').sortable('refresh') рд╡рд┐рдзрд┐, рдЬреЛ рдмрд╣реБрдд рдЕрд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рд╣реИред

рдЪреВрдВрдХрд┐ рдореЗрд░реЗ рдХрд╛рд░реНрдп рдХреЛ рдкреБрд░рд╛рдиреЗ рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рдХреЗ рд╕рдорд░реНрдерди рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рдереА, рдЗрд╕рд▓рд┐рдП рдореИрдВрдиреЗ HTML5 Drag'n'Drop рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╢реБрджреНрдз рдЬреЗрдПрд╕ рдореЗрдВ рдЖрд╡рд╢реНрдпрдХ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдмрдирд╛рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛ред

рдЗрд╕ рд╡рд┐рд╖рдп рдкрд░ рд▓реЗрдЦ рдкрдврд╝рдиреЗ рдХреЗ рдмрд╛рдж, рдпрд╣ рдкрддрд╛ рдЪрд▓рд╛ рд╣реИ рдХрд┐ рдЕрдм рдЗрд╕ рддрд░рд╣ рдХреЗ рдХрд╛рд░реНрдпрд╛рддреНрдордХ рдмрдирд╛рдирд╛ рдмрд╣реБрдд рдЖрд╕рд╛рди рд╣реИ, рдЖрдк 25 рдкрдВрдХреНрддрд┐рдпреЛрдВ рдХреЛ рднреА рдкреВрд░рд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ (рдпрджрд┐ рдЖрдк рдЯрд┐рдкреНрдкрдгрд┐рдпреЛрдВ рдФрд░ рдмреАрдЯреНрд╕ рдХреЛ рд╣рдЯрд╛рддреЗ рд╣реИрдВ):

http://jsfiddle.net/RubaXa/zLq5J/
 function sortable(rootEl, onUpdate){ var dragEl; //     [].slice.call(rootEl.children).forEach(function (itemEl){ itemEl.draggable = true; }); //     function _onDragOver(evt){ evt.preventDefault(); evt.dataTransfer.dropEffect = 'move'; var target = evt.target; if( target && target !== dragEl && target.nodeName == 'LI' ){ //  rootEl.insertBefore(dragEl, target.nextSibling || target); } } //   function _onDragEnd(evt){ evt.preventDefault(); dragEl.classList.remove('ghost'); rootEl.removeEventListener('dragover', _onDragOver, false); rootEl.removeEventListener('dragend', _onDragEnd, false); //     onUpdate(dragEl); } //   rootEl.addEventListener('dragstart', function (evt){ dragEl = evt.target; //      //    evt.dataTransfer.effectAllowed = 'move'; evt.dataTransfer.setData('Text', dragEl.textContent); //     dnd rootEl.addEventListener('dragover', _onDragOver, false); rootEl.addEventListener('dragend', _onDragEnd, false); setTimeout(function (){ //      setTimeout,  //  ,    . dragEl.classList.add('ghost'); }, 0) }, false); } //  sortable( document.getElementById('list'), function (item){ console.log(item); }); 

рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рдХреЛрдб рд╕реЗ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдкреВрд░реА рдЫрдБрдЯрд╛рдИ рдореЗрдВ рдХреЗрд╡рд▓ rootEl.insertBefore(dragEl, target.nextSibling || target) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ rootEl.insertBefore(dragEl, target.nextSibling || target) , рдЬрд╣рд╛рдБ target рд╡рд╣ рддрддреНрд╡ рд╣реИ рдЬрд┐рд╕реЗ rootEl.insertBefore(dragEl, target.nextSibling || target) ред рдпрджрд┐ рдЖрдкрдиреЗ рдкрд╣рд▓реЗ рд╣реА рдЙрджрд╛рд╣рд░рдг рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░ рд▓рд┐рдпрд╛ рд╣реИ, рддреЛ рд╕рдВрднрд╡рддрдГ рдЖрдкрдиреЗ рджреЗрдЦрд╛ рд╣реИ рдХрд┐ рдЖрдк рдХрд┐рд╕реА рддрддреНрд╡ рдХреЛ рдкрд╣рд▓реА рд╕реНрдерд┐рддрд┐ рдореЗрдВ рдирд╣реАрдВ рдЦреАрдВрдЪ рд╕рдХрддреЗред рд╡рд┐рдзрд┐ рдХреА рдПрдХ рдФрд░ рдмрд╛рд░реАрдХрд┐рдпреЛрдВ - onUpdate рдХреЛ рд╣рд░ рдмрд╛рд░ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ, рднрд▓реЗ рд╣реА рдЖрдЗрдЯрдо рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реЛред

рдкрд╣рд▓реА рд╕рдорд╕реНрдпрд╛ рд╕реЗ рдЫреБрдЯрдХрд╛рд░рд╛ рдкрд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдмрд╕ рдЫрдВрдЯрд╛рдИ рдХреЗ рджреМрд░рд╛рди рдПрдХ рдЬрд╛рдВрдЪ рдЬреЛрдбрд╝реЗрдВред рдХрд┐рд╕реА рддрддреНрд╡ target.nextSibling рдмрд╛рдж target.nextSibling рдХреЗрд╡рд▓ рдЗрд╕ рд╕реВрдЪреА рдХрд╛ рдкрд╣рд▓рд╛ рддрддреНрд╡ рдирд╣реАрдВ рд╣реИред

http://jsfiddle.net/RubaXa/zLq5J/3/
 if( target && target !== dragEl && target.nodeName == 'LI' ){ //  rootEl.insertBefore(dragEl, rootEl.children[0] !== target && target.nextSibling || target); } 

рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдмрд╕ nextEl = dragEl.nextSibling рдХреЗ рд╕рдордп рдЕрдЧрд▓реЗ рддрддреНрд╡ ( nextEl = dragEl.nextSibling ) рдХреЗ рд▓рд┐рдВрдХ рдХреЛ рд╕рд╣реЗрдЬрдиреЗ рд╕реЗ рдЖрдкрдХреЛ рджреВрд╕рд░реА рд╕рдорд╕реНрдпрд╛ (http://jsfiddle.net/RubaXa/zdq5J/4/29 рдФрд░ рд▓рд╛рдЗрди 38) рд╕реЗ рдЫреБрдЯрдХрд╛рд░рд╛ рдорд┐рд▓ рд╕рдХрддрд╛ рд╣реИред

рдкрд╣рд▓реА рдирдЬрд╝рд░ рдореЗрдВ, рд╕рдм рдХреБрдЫ рдЕрдЪреНрдЫрд╛ рд▓рдЧ рд░рд╣рд╛ рд╣реИ, рдпрд╣ рдХреЙрдореНрдкреИрдХреНрдЯ рдФрд░ рд╕реНрдкрд╖реНрдЯ рдХреЛрдб рд╣реИ рдЬреЛ рдЕрдзрд┐рдХрд╛рдВрд╢ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдпрджрд┐ рдЖрдк attachEvent рд╕рдорд░реНрдерди рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ рдФрд░ dragEl.classList.add/remove рд╣рдЯрд╛ рджреЗрдВ dragEl.classList.add/remove , рдХреЛрдб IE5.5 рдореЗрдВ рднреА рдХрд╛рдо рдХрд░реЗрдЧрд╛:]

рд▓реЗрдХрд┐рди, рдпрджрд┐ рд╣рдо рдЙрджрд╛рд╣рд░рдг рдХреЛ рдереЛрдбрд╝рд╛ рдмрджрд▓рддреЗ рд╣реИрдВ, рддреЛ рдмрд╕ рд╕реВрдЪреА рдХреА рд╡рд╕реНрддреБрдУрдВ рдХреА рдКрдВрдЪрд╛рдИ рдмрдврд╝рд╛рддреЗ рд╣реБрдП, рд╣рдореЗрдВ рддреАрд╕рд░реА рд╕рдорд╕реНрдпрд╛ рдорд┐рд▓рддреА рд╣реИред рдЫрдВрдЯрдиреА рд╕рд╛рдорд╛рдиреНрдп рд░реВрдк рд╕реЗ рдКрдкрд░ рд╕реЗ рдиреАрдЪреЗ рддрдХ рдХрд╛рдо рдХрд░рддреА рд╣реИ, рд▓реЗрдХрд┐рди рдЗрд╕рдХреЗ рд╡рд┐рдкрд░реАрдд - рдпрд╣ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдЦрд░рд╛рдм рд╣реИред рдЗрд╕рд▓рд┐рдП, рддрддреНрд╡ рдХреЗ рд╕рдореНрдорд┐рд▓рди, "рдкрд╣рд▓реЗ" рдпрд╛ "рдмрд╛рдж" рдХреЛ рдЪреБрдирдиреЗ рдХреЗ рддрд░реНрдХ рдХреЛ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рддрд╛рдХрд┐ рдпрд╣ рдзреНрдпрд╛рди рдореЗрдВ рд░рдЦреЗ рдХрд┐ рдорд╛рдЙрд╕ рдХрд░реНрд╕рд░ рдХрд╛ рдЖрдзрд╛ рднрд╛рдЧ "рд╢реАрд░реНрд╖" рдпрд╛ "рдиреАрдЪреЗ" рд╕реНрдерд┐рдд рд╣реИред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, onDragOver рдкрд░ рд╣рдореЗрдВ рд╕реНрдХреНрд░реАрди рдХреЗ рд╕рд╛рдкреЗрдХреНрд╖ рддрддреНрд╡ рдХреЗ рдирд┐рд░реНрджреЗрд╢рд╛рдВрдХ рдорд┐рд▓рддреЗ рд╣реИрдВ рдФрд░ рдЬрд╛рдВрдЪрддреЗ рд╣реИрдВ рдХрд┐ рдЖрдзрд╛ рдХрд░реНрд╕рд░ рдХреМрди рд╕рд╛ рд╣реИ:

http://jsfiddle.net/RubaXa/zLq5J/6/
 var rect = target.getBoundingClientRect(); var next = (evt.clientY - rect.top)/(rect.bottom - rect.top) > .5; rootEl.insertBefore(dragEl, next && target.nextSibling || target); 

рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдореБрдЭреЗ рдЗрдирд▓рд╛рдЗрди рддрддреНрд╡реЛрдВ рдФрд░ рдлреНрд▓реЛрдЯ рдмреНрд▓реЙрдХреЛрдВ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХреЛ рднреА рдкрд░рд┐рд╖реНрдХреГрдд рдХрд░рдирд╛ рдкрдбрд╝рд╛ред


рд╕реНрдкрд░реНрд╢ рд╕рдорд░реНрдерди

рдХрд╛рд╢, drag'n'drop рд╕реНрдкрд░реНрд╢ рдЙрдкрдХрд░рдгреЛрдВ рдкрд░ рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ, рддреЛ рдХрд┐рд╕реА рддрд░рд╣ рд╣рдо рд╕реНрдкрд░реНрд╢ рдШрдЯрдирд╛рдУрдВ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдЕрдиреБрдХрд░рдг рдХрд░рдирд╛ рдерд╛ред рдореИрдВрдиреЗ рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдЕрдкрдиреЗ рджрд┐рдорд╛рдЧ рдХреЛ рд░реИрдХ рдХрд┐рдпрд╛, рджрд╕реНрддрд╛рд╡реЗрдЬрд╝реАрдХрд░рдг рдкрдврд╝рд╛, рд▓реЗрдХрд┐рди рдЬрд╡рд╛рдм рдирд╣реАрдВ рдорд┐рд▓рд╛ред рдирддреАрдЬрддрди, рдереЛрдбрд╝реА рд╕реА рдЦреБрджрд╛рдИ рдХреЗ рдмрд╛рдж, рдореБрдЭреЗ рдЕрджреНрднреБрдд рджрд╕реНрддрд╛рд╡реЗрдЬ рдпрд╛рдж рдЖрдпрд╛ редelementFromPoint рд╡рд┐рдзрд┐ , рдЬреЛ рдЖрдкрдХреЛ рдирд┐рд░реНрджреЗрд╢рд╛рдВрдХ рджреНрд╡рд╛рд░рд╛ рдПрдХ рддрддреНрд╡ рдХрд╛ рд▓рд┐рдВрдХ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред

рдирддреАрдЬрддрди, touchstart рдореИрдВ рдПрдХ рддрддреНрд╡ рдХреЛ рдХреНрд▓реЛрди рдХрд░рддрд╛ рд╣реВрдВ рдЬреЛ рдореЗрд░реА рдЙрдВрдЧрд▓реА рдХреЗ рдиреАрдЪреЗ рдПрдХ "рднреВрдд" рдХреЗ рд░реВрдк рдореЗрдВ рдХрд╛рдо рдХрд░реЗрдЧрд╛ рдФрд░ рдЗрд╕реЗ touchmove рдкрд░ translate3d рдХрд╛ рдЙрдкрдпреЛрдЧ touchmove рд▓реЗ рдЬрд╛рдПрдЧрд╛:

 var touch = evt.touches[0] , dx = touch.clientX - tapEvt.clientX , dy = touch.clientY - tapEvt.clientY ; 

рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдореИрдВ setInterval рдЪрд▓рд╛рддрд╛ рд╣реВрдВ, рдЬрд┐рд╕рдореЗрдВ рдкреНрд░рддреНрдпреЗрдХ 100ms рдореИрдВ рдЙрд╕ рддрддреНрд╡ рдХреА рдЬрд╛рдВрдЪ рдХрд░рддрд╛ рд╣реВрдВ, рдЬрд┐рд╕ рдкрд░ рдореЗрд░реА рдЙрдВрдЧрд▓реА рд╡рд░реНрддрдорд╛рди рдореЗрдВ рд╣реИ:

 _emulateDragOver: function (){ if( touchEvt ){ //  тАЬтАЭ   _css(ghostEl, 'display', 'none'); //  ,     var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY); //        rootEl, //   onDragOver: this._onDragOver({ target: target , clientX: touchEvt.clientX , clientY: touchEvt.clientY }); //   тАЬтАЭ _css(ghostEl, 'display', ''); } } 

рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд░рд╣реЗ рд╣реИрдВ, рдпрд╣ рд╕рдм рдХреБрдЫ рдЕрд▓реМрдХрд┐рдХ рдирд╣реАрдВ рд╣реИред рд╣рдо рдХреЛрдб рдмрдирд╛рддреЗ рд╣реИрдВ, рдереЛрдбрд╝рд╛ рджрд╕реНрддрд╛рд╡реЗрдЬ рд▓рд┐рдЦрддреЗ рд╣реИрдВ рдФрд░ рдорд╛рдЗрдХреНрд░реЛ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рддреИрдпрд╛рд░ рд╣реЛрддреА рд╣реИред


рдХреНрд░рдорд┐рдд рдХрд░рдиреЗ рдпреЛрдЧреНрдп

рдкреБрд╕реНрддрдХрд╛рд▓рдп 2 kb рдХрд╛ рд╣реЛ рдЧрдпрд╛ рдФрд░ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╡рд┐рд╢реЗрд╖рддрд╛рдПрдВ рд╣реИрдВ:



рдХреЛрдб рдЙрджрд╛рд╣рд░рдг:

 //  ,  ul > li var list = document.getElementById("my-ui-list"); new Sortable(list); //  . //  var foo = document.getElementById("foo"); new Sortable(foo, { group: "omega" }); var bar = document.getElementById("bar"); new Sortable(bar, { group: "omega" }); // handle + event var container = document.getElementById("multi"); new Sortable(container, { handle: ".tile__title", // css-,     dragabble: ".tile", // css- ,    onUpdate: function (evt/**Event*/){ var item = evt.detail; //   ,   } }); 


рдлрд┐рд▓рд╣рд╛рд▓, рдХреЗрд╡рд▓ рдмреБрдирд┐рдпрд╛рджреА рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рд╣реИ, рдореБрдЭреЗ рдХрд┐рд╕реА рднреА рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдпрд╛ рдкреБрд▓ рдЕрдиреБрд░реЛрдз рдкрд░ рдЦреБрд╢реА рд╣реЛрдЧреА, рдЖрдкрдХреЗ рдзреНрдпрд╛рди рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред


рдбреЗрдореЛ | рд╕реНрд░реЛрдд



рдЖрдк рд╣рдорд╛рд░реА рдкрд░рд┐рдпреЛрдЬрдирд╛рдУрдВ рдХрд╛ рднреА рдЕрдиреБрд╕рд░рдг рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:
github.com/mailru - FileAPI, рдЯрд╛рд░рдирдЯреВрд▓, рдЙрддреНрд╕рд╡ рдФрд░ рдмрд╣реБрдд рдХреБрдЫ
github.com/rubaxa - рдореЗрд░рд╛ рдЬреАрдереВрдм
@ibnRubaXa

Source: https://habr.com/ru/post/In207048/


All Articles