├── .editorconfig ├── .gitignore ├── CNAME ├── README.md ├── build.sh ├── build ├── artslist.js ├── bs │ ├── bbcode.js │ ├── build.js │ ├── js │ │ ├── bootstrap.js │ │ ├── custom.js │ │ ├── jquery.js │ │ └── main.js │ ├── rollup.config.js │ ├── scss │ │ ├── bootstrap-theme.scss │ │ ├── bootstrap.scss │ │ ├── custom.scss │ │ └── main.scss │ └── templates │ │ ├── index.html │ │ ├── page.html │ │ └── tutorial.html ├── generate-markdown.js ├── generate-pdf.js ├── pages │ ├── polityka-prywatnosci.md │ └── polityka-prywatnosci.tpl ├── pageslist.js ├── tutorials │ ├── html5-blog.md │ ├── js-beauty.md │ ├── js-dynamic.md │ ├── js-hiding.md │ ├── js-intl.md │ ├── js-jquery.md │ ├── polymer.md │ └── svg-sprites.md └── tutslist.js ├── package-lock.json ├── package.json └── public ├── .htaccess ├── css ├── main.css └── prism.css ├── favicon.ico ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf ├── glyphicons-halflings-regular.woff └── glyphicons-halflings-regular.woff2 ├── google46fa397f2bc00dac.html ├── html5-blog.html ├── html5-blog.pdf ├── images ├── comandeer.jpg └── line.png ├── index.html ├── js-beauty.html ├── js-beauty.pdf ├── js-dynamic.html ├── js-dynamic.pdf ├── js-hiding.html ├── js-hiding.pdf ├── js-intl.html ├── js-intl.pdf ├── js-jquery.html ├── js-jquery.pdf ├── js ├── cookies.js ├── main.js ├── main.js.map └── prism.js ├── polityka-prywatnosci.html ├── polityka-prywatnosci.pdf ├── polymer.html ├── polymer.pdf ├── res ├── html5-blog │ ├── ferrante-book.pdf │ ├── final.html │ ├── outlinehtml4.png │ ├── outlinehtml5.png │ └── start.html └── svg-sprites │ ├── index.html │ └── stack.svg ├── robots.txt ├── svg-sprites.html └── svg-sprites.pdf /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.*~ 3 | *.db 4 | .goutputstream-* 5 | *.bac 6 | /.project 7 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | tutorials.comandeer.pl 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Comandeer's tutorials 2 | 3 | It's a new home for my tutorials, yay! 4 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | TYPE=bs 3 | if [[ $1 != "" ]]; then 4 | TYPE=$1 5 | fi 6 | 7 | cd build/$TYPE 8 | node build 9 | -------------------------------------------------------------------------------- /build/artslist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'Asynchroniczna synchroniczność': 'http://webroad.pl/javascript/746-synchroniczna-asynchronicznosc', 3 | 'Web (of) Intents – czego brakuje dzisiejszej Sieci?': 'http://webroad.pl/inne/3035-web-of-intents-czego-brakuje-dzisiejszej-sieci', 4 | 'Web Components': 'http://webroad.pl/javascript/3505-web-components', 5 | 'Progressive Enhancement – zapomniany fundament': 'http://webroad.pl/inne/3722-progressive-enhancement-zapomniany-fundament', 6 | 'To validate or not to validate – that is the question!': 'http://webroad.pl/inne/3925-validate-or-not-to-validate-that-is-the-question', 7 | 'Filtrowanie HTML przy pomocy selektorów CSS': 'http://devcorner.pl/filtrowanie-html-przy-pomocy-selektorow-css/', 8 | 'The great world of open Web standards': 'https://medium.com/content-uneditable/the-great-world-of-open-web-standards-64c1fe53063', 9 | 'How we mocked improving JavaScript': 'https://medium.com/content-uneditable/how-we-mocked-improving-javascript-7b4cd876167' 10 | }; 11 | -------------------------------------------------------------------------------- /build/bs/build.js: -------------------------------------------------------------------------------- 1 | var oldCwd = process.cwd(); 2 | process.chdir( __dirname ); 3 | 4 | var fs = require( 'fs' ), 5 | tutDir = '../tutorials/', 6 | pagesDir = '../pages/', 7 | pageTemplate = fs.readFileSync( './templates/page.html', 'utf8' ), 8 | tutTemplate = fs.readFileSync( './templates/tutorial.html', 'utf8' ), 9 | parser = require('markdown-it')( { 10 | html: true, 11 | linkify: true 12 | } ), 13 | mila = require('markdown-it-link-attributes'), 14 | dom = require( 'cheerio' ), 15 | tutorials = fs.readdirSync( tutDir ), 16 | pages = fs.readdirSync( pagesDir ), 17 | pagesList = require( '../pageslist' ), 18 | siteMenu = ''; 19 | 20 | parser.use( mila, { 21 | attrs: { 22 | rel: 'noreferrer noopener' 23 | } 24 | } ); 25 | 26 | function getBuilder( type = 'tutorial' ) { 27 | return function( page ) { 28 | var dir = type === 'tutorial' ? tutDir : pagesDir, 29 | content = fs.readFileSync( dir + page, 'utf8' ), 30 | output = type === 'tutorial' ? tutTemplate : pageTemplate, 31 | pageName = page.replace( /(\.html|\.md)$/, '' ), 32 | nav = ``, 40 | offset = 0, 41 | $ul = dom.load( nav )( 'ul' ), 42 | content = parser.render( content ); 43 | 44 | var $ = dom.load( content ), 45 | lastDepth = null, 46 | currentSubmenu = $ul; 47 | 48 | $ul.html( '' ); 49 | 50 | // Bootstrap hack for http://getbootstrap.com/components/#alerts-links 51 | $( '.alert a' ).each( function() { 52 | $( this ).addClass( 'alert-link' ); 53 | } ); 54 | 55 | $( 'description' ).each( function() { 56 | output = output.replace( /{DESCRIPTION}/g, $( this ).text() ); 57 | $( this ).remove(); 58 | } ); 59 | 60 | $( 'h1, h2, h3, h4, h5, h6' ).each( function() { 61 | var $this = $( this ), 62 | depth = Number( $this[ 0 ].name.substring( 1 ) ), 63 | lastElem = currentSubmenu.children( 'li' ).last(), 64 | html = '', 65 | name = $this.html().replace( /.+?<\/a>/gi, '' ); 66 | 67 | html = '
  • ' + name + '
  • '; 68 | 69 | if( $this.is( '#start' ) ) { 70 | output = output.replace( /{TITLE}/g, $this.html().replace( /.+?<\/a>/gi, '' ) ); 71 | $this.remove(); 72 | } 73 | 74 | if ( lastDepth ) { 75 | if ( lastDepth < depth ) { 76 | currentSubmenu = $( '' ); 77 | 78 | lastElem.append( currentSubmenu ); 79 | } else if ( lastDepth > depth ) { 80 | while ( lastDepth-- > depth ) { 81 | currentSubmenu = currentSubmenu.parent().parent( 'ul' ); 82 | } 83 | } 84 | } 85 | 86 | currentSubmenu.append( html ); 87 | lastDepth = depth; 88 | } ); 89 | 90 | if ( $ul.children().length > 0 ) { 91 | nav = nav.replace( '{NAV}', $ul.html() ); 92 | } else { 93 | nav = ''; 94 | offset = 2; 95 | } 96 | 97 | output = output.replace( /{SLUG}/g, pageName ); 98 | output = output.replace( /{MENU}/g, siteMenu ); 99 | output = output.replace( '{NAV}', nav ); 100 | output = output.replace( '{OFFSET}', offset ); 101 | output = output.replace( '{HEADING_OFFSET}', offset || 4 ); 102 | output = output.replace( '{CONTENT}', $.html() ); 103 | output = output.replace( '{DISQUS}', pageName ); 104 | 105 | fs.writeFileSync( `../../public/${ pageName }.html`, output, 'utf8' ); 106 | } 107 | } 108 | 109 | // building site menu 110 | Object.keys( pagesList ).forEach( function( page ) { 111 | var pageInfo = pagesList[ page ]; 112 | siteMenu += `
  • ${ page }
  • ` 113 | } ); 114 | 115 | tutorials.filter( ( tutorial ) => { 116 | return tutorial.endsWith( '.md' ); 117 | } ).forEach( getBuilder( 'tutorial' ) ); 118 | pages.filter( ( page ) => { 119 | return page.endsWith( '.md' ); 120 | } ).forEach( getBuilder( 'page' ) ); 121 | 122 | // building list of tutorials 123 | var list = fs.readFileSync( './templates/index.html', 'utf8' ), 124 | tuts = require( '../tutslist' ), 125 | arts = require( '../artslist' ), 126 | response = '', 127 | artsr = ''; 128 | 129 | Object.keys( tuts ).forEach( function( t ) { 130 | var tut = tuts[ t ]; 131 | 132 | response += '
    ' + t + '
    '; 133 | 134 | Object.keys( tut ).forEach( function( x ) { 135 | response += `
    136 | ${x} 137 |
    `; 138 | } ); 139 | } ); 140 | 141 | Object.keys( arts ).reverse().forEach( function( t ) { 142 | artsr += `
  • 143 | ${t} 144 |
  • `; 145 | } ); 146 | 147 | list = list.replace( '{LIST}', response ); 148 | list = list.replace( '{ARTS}', artsr ); 149 | list = list.replace( '{MENU}', siteMenu ); 150 | 151 | fs.writeFileSync( '../../public/index.html', list, 'utf8' ); 152 | 153 | process.chdir( oldCwd ); 154 | -------------------------------------------------------------------------------- /build/bs/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/) 3 | */ 4 | 5 | /*! 6 | * Bootstrap v3.4.1 (https://getbootstrap.com/) 7 | * Copyright 2011-2019 Twitter, Inc. 8 | * Licensed under the MIT license 9 | */ 10 | 11 | if (typeof jQuery === 'undefined') { 12 | throw new Error('Bootstrap\'s JavaScript requires jQuery') 13 | } 14 | +function ($) { 15 | 'use strict'; 16 | var version = $.fn.jquery.split(' ')[0].split('.') 17 | if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) { 18 | throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4') 19 | } 20 | }(jQuery); 21 | 22 | /* ======================================================================== 23 | * Bootstrap: affix.js v3.4.1 24 | * https://getbootstrap.com/docs/3.4/javascript/#affix 25 | * ======================================================================== 26 | * Copyright 2011-2019 Twitter, Inc. 27 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 28 | * ======================================================================== */ 29 | 30 | 31 | +function ($) { 32 | 'use strict'; 33 | 34 | // AFFIX CLASS DEFINITION 35 | // ====================== 36 | 37 | var Affix = function (element, options) { 38 | this.options = $.extend({}, Affix.DEFAULTS, options) 39 | 40 | var target = this.options.target === Affix.DEFAULTS.target ? $(this.options.target) : $(document).find(this.options.target) 41 | 42 | this.$target = target 43 | .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) 44 | .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) 45 | 46 | this.$element = $(element) 47 | this.affixed = null 48 | this.unpin = null 49 | this.pinnedOffset = null 50 | 51 | this.checkPosition() 52 | } 53 | 54 | Affix.VERSION = '3.4.1' 55 | 56 | Affix.RESET = 'affix affix-top affix-bottom' 57 | 58 | Affix.DEFAULTS = { 59 | offset: 0, 60 | target: window 61 | } 62 | 63 | Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { 64 | var scrollTop = this.$target.scrollTop() 65 | var position = this.$element.offset() 66 | var targetHeight = this.$target.height() 67 | 68 | if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false 69 | 70 | if (this.affixed == 'bottom') { 71 | if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' 72 | return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' 73 | } 74 | 75 | var initializing = this.affixed == null 76 | var colliderTop = initializing ? scrollTop : position.top 77 | var colliderHeight = initializing ? targetHeight : height 78 | 79 | if (offsetTop != null && scrollTop <= offsetTop) return 'top' 80 | if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' 81 | 82 | return false 83 | } 84 | 85 | Affix.prototype.getPinnedOffset = function () { 86 | if (this.pinnedOffset) return this.pinnedOffset 87 | this.$element.removeClass(Affix.RESET).addClass('affix') 88 | var scrollTop = this.$target.scrollTop() 89 | var position = this.$element.offset() 90 | return (this.pinnedOffset = position.top - scrollTop) 91 | } 92 | 93 | Affix.prototype.checkPositionWithEventLoop = function () { 94 | setTimeout($.proxy(this.checkPosition, this), 1) 95 | } 96 | 97 | Affix.prototype.checkPosition = function () { 98 | if (!this.$element.is(':visible')) return 99 | 100 | var height = this.$element.height() 101 | var offset = this.options.offset 102 | var offsetTop = offset.top 103 | var offsetBottom = offset.bottom 104 | var scrollHeight = Math.max($(document).height(), $(document.body).height()) 105 | 106 | if (typeof offset != 'object') offsetBottom = offsetTop = offset 107 | if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) 108 | if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) 109 | 110 | var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) 111 | 112 | if (this.affixed != affix) { 113 | if (this.unpin != null) this.$element.css('top', '') 114 | 115 | var affixType = 'affix' + (affix ? '-' + affix : '') 116 | var e = $.Event(affixType + '.bs.affix') 117 | 118 | this.$element.trigger(e) 119 | 120 | if (e.isDefaultPrevented()) return 121 | 122 | this.affixed = affix 123 | this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null 124 | 125 | this.$element 126 | .removeClass(Affix.RESET) 127 | .addClass(affixType) 128 | .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') 129 | } 130 | 131 | if (affix == 'bottom') { 132 | this.$element.offset({ 133 | top: scrollHeight - height - offsetBottom 134 | }) 135 | } 136 | } 137 | 138 | 139 | // AFFIX PLUGIN DEFINITION 140 | // ======================= 141 | 142 | function Plugin(option) { 143 | return this.each(function () { 144 | var $this = $(this) 145 | var data = $this.data('bs.affix') 146 | var options = typeof option == 'object' && option 147 | 148 | if (!data) $this.data('bs.affix', (data = new Affix(this, options))) 149 | if (typeof option == 'string') data[option]() 150 | }) 151 | } 152 | 153 | var old = $.fn.affix 154 | 155 | $.fn.affix = Plugin 156 | $.fn.affix.Constructor = Affix 157 | 158 | 159 | // AFFIX NO CONFLICT 160 | // ================= 161 | 162 | $.fn.affix.noConflict = function () { 163 | $.fn.affix = old 164 | return this 165 | } 166 | 167 | 168 | // AFFIX DATA-API 169 | // ============== 170 | 171 | $(window).on('load', function () { 172 | $('[data-spy="affix"]').each(function () { 173 | var $spy = $(this) 174 | var data = $spy.data() 175 | 176 | data.offset = data.offset || {} 177 | 178 | if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom 179 | if (data.offsetTop != null) data.offset.top = data.offsetTop 180 | 181 | Plugin.call($spy, data) 182 | }) 183 | }) 184 | 185 | }(jQuery); 186 | 187 | /* ======================================================================== 188 | * Bootstrap: collapse.js v3.4.1 189 | * https://getbootstrap.com/docs/3.4/javascript/#collapse 190 | * ======================================================================== 191 | * Copyright 2011-2019 Twitter, Inc. 192 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 193 | * ======================================================================== */ 194 | 195 | /* jshint latedef: false */ 196 | 197 | +function ($) { 198 | 'use strict'; 199 | 200 | // COLLAPSE PUBLIC CLASS DEFINITION 201 | // ================================ 202 | 203 | var Collapse = function (element, options) { 204 | this.$element = $(element) 205 | this.options = $.extend({}, Collapse.DEFAULTS, options) 206 | this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + 207 | '[data-toggle="collapse"][data-target="#' + element.id + '"]') 208 | this.transitioning = null 209 | 210 | if (this.options.parent) { 211 | this.$parent = this.getParent() 212 | } else { 213 | this.addAriaAndCollapsedClass(this.$element, this.$trigger) 214 | } 215 | 216 | if (this.options.toggle) this.toggle() 217 | } 218 | 219 | Collapse.VERSION = '3.4.1' 220 | 221 | Collapse.TRANSITION_DURATION = 350 222 | 223 | Collapse.DEFAULTS = { 224 | toggle: true 225 | } 226 | 227 | Collapse.prototype.dimension = function () { 228 | var hasWidth = this.$element.hasClass('width') 229 | return hasWidth ? 'width' : 'height' 230 | } 231 | 232 | Collapse.prototype.show = function () { 233 | if (this.transitioning || this.$element.hasClass('in')) return 234 | 235 | var activesData 236 | var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') 237 | 238 | if (actives && actives.length) { 239 | activesData = actives.data('bs.collapse') 240 | if (activesData && activesData.transitioning) return 241 | } 242 | 243 | var startEvent = $.Event('show.bs.collapse') 244 | this.$element.trigger(startEvent) 245 | if (startEvent.isDefaultPrevented()) return 246 | 247 | if (actives && actives.length) { 248 | Plugin.call(actives, 'hide') 249 | activesData || actives.data('bs.collapse', null) 250 | } 251 | 252 | var dimension = this.dimension() 253 | 254 | this.$element 255 | .removeClass('collapse') 256 | .addClass('collapsing')[dimension](0) 257 | .attr('aria-expanded', true) 258 | 259 | this.$trigger 260 | .removeClass('collapsed') 261 | .attr('aria-expanded', true) 262 | 263 | this.transitioning = 1 264 | 265 | var complete = function () { 266 | this.$element 267 | .removeClass('collapsing') 268 | .addClass('collapse in')[dimension]('') 269 | this.transitioning = 0 270 | this.$element 271 | .trigger('shown.bs.collapse') 272 | } 273 | 274 | if (!$.support.transition) return complete.call(this) 275 | 276 | var scrollSize = $.camelCase(['scroll', dimension].join('-')) 277 | 278 | this.$element 279 | .one('bsTransitionEnd', $.proxy(complete, this)) 280 | .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) 281 | } 282 | 283 | Collapse.prototype.hide = function () { 284 | if (this.transitioning || !this.$element.hasClass('in')) return 285 | 286 | var startEvent = $.Event('hide.bs.collapse') 287 | this.$element.trigger(startEvent) 288 | if (startEvent.isDefaultPrevented()) return 289 | 290 | var dimension = this.dimension() 291 | 292 | this.$element[dimension](this.$element[dimension]())[0].offsetHeight 293 | 294 | this.$element 295 | .addClass('collapsing') 296 | .removeClass('collapse in') 297 | .attr('aria-expanded', false) 298 | 299 | this.$trigger 300 | .addClass('collapsed') 301 | .attr('aria-expanded', false) 302 | 303 | this.transitioning = 1 304 | 305 | var complete = function () { 306 | this.transitioning = 0 307 | this.$element 308 | .removeClass('collapsing') 309 | .addClass('collapse') 310 | .trigger('hidden.bs.collapse') 311 | } 312 | 313 | if (!$.support.transition) return complete.call(this) 314 | 315 | this.$element 316 | [dimension](0) 317 | .one('bsTransitionEnd', $.proxy(complete, this)) 318 | .emulateTransitionEnd(Collapse.TRANSITION_DURATION) 319 | } 320 | 321 | Collapse.prototype.toggle = function () { 322 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 323 | } 324 | 325 | Collapse.prototype.getParent = function () { 326 | return $(document).find(this.options.parent) 327 | .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') 328 | .each($.proxy(function (i, element) { 329 | var $element = $(element) 330 | this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) 331 | }, this)) 332 | .end() 333 | } 334 | 335 | Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { 336 | var isOpen = $element.hasClass('in') 337 | 338 | $element.attr('aria-expanded', isOpen) 339 | $trigger 340 | .toggleClass('collapsed', !isOpen) 341 | .attr('aria-expanded', isOpen) 342 | } 343 | 344 | function getTargetFromTrigger($trigger) { 345 | var href 346 | var target = $trigger.attr('data-target') 347 | || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 348 | 349 | return $(document).find(target) 350 | } 351 | 352 | 353 | // COLLAPSE PLUGIN DEFINITION 354 | // ========================== 355 | 356 | function Plugin(option) { 357 | return this.each(function () { 358 | var $this = $(this) 359 | var data = $this.data('bs.collapse') 360 | var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) 361 | 362 | if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false 363 | if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) 364 | if (typeof option == 'string') data[option]() 365 | }) 366 | } 367 | 368 | var old = $.fn.collapse 369 | 370 | $.fn.collapse = Plugin 371 | $.fn.collapse.Constructor = Collapse 372 | 373 | 374 | // COLLAPSE NO CONFLICT 375 | // ==================== 376 | 377 | $.fn.collapse.noConflict = function () { 378 | $.fn.collapse = old 379 | return this 380 | } 381 | 382 | 383 | // COLLAPSE DATA-API 384 | // ================= 385 | 386 | $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { 387 | var $this = $(this) 388 | 389 | if (!$this.attr('data-target')) e.preventDefault() 390 | 391 | var $target = getTargetFromTrigger($this) 392 | var data = $target.data('bs.collapse') 393 | var option = data ? 'toggle' : $this.data() 394 | 395 | Plugin.call($target, option) 396 | }) 397 | 398 | }(jQuery); 399 | 400 | /* ======================================================================== 401 | * Bootstrap: transition.js v3.4.1 402 | * https://getbootstrap.com/docs/3.4/javascript/#transitions 403 | * ======================================================================== 404 | * Copyright 2011-2019 Twitter, Inc. 405 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 406 | * ======================================================================== */ 407 | 408 | 409 | +function ($) { 410 | 'use strict'; 411 | 412 | // CSS TRANSITION SUPPORT (Shoutout: https://modernizr.com/) 413 | // ============================================================ 414 | 415 | function transitionEnd() { 416 | var el = document.createElement('bootstrap') 417 | 418 | var transEndEventNames = { 419 | WebkitTransition : 'webkitTransitionEnd', 420 | MozTransition : 'transitionend', 421 | OTransition : 'oTransitionEnd otransitionend', 422 | transition : 'transitionend' 423 | } 424 | 425 | for (var name in transEndEventNames) { 426 | if (el.style[name] !== undefined) { 427 | return { end: transEndEventNames[name] } 428 | } 429 | } 430 | 431 | return false // explicit for ie8 ( ._.) 432 | } 433 | 434 | // https://blog.alexmaccaw.com/css-transitions 435 | $.fn.emulateTransitionEnd = function (duration) { 436 | var called = false 437 | var $el = this 438 | $(this).one('bsTransitionEnd', function () { called = true }) 439 | var callback = function () { if (!called) $($el).trigger($.support.transition.end) } 440 | setTimeout(callback, duration) 441 | return this 442 | } 443 | 444 | $(function () { 445 | $.support.transition = transitionEnd() 446 | 447 | if (!$.support.transition) return 448 | 449 | $.event.special.bsTransitionEnd = { 450 | bindType: $.support.transition.end, 451 | delegateType: $.support.transition.end, 452 | handle: function (e) { 453 | if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) 454 | } 455 | } 456 | }) 457 | 458 | }(jQuery); 459 | -------------------------------------------------------------------------------- /build/bs/js/custom.js: -------------------------------------------------------------------------------- 1 | try { 2 | var codes = document.getElementsByClassName( 'code' ); 3 | 4 | [].forEach.call( codes, function( code ) { 5 | var next = code.nextElementSibling; 6 | 7 | if ( !next || next.tagName.toLowerCase() !== 'pre' || next.scrollHeight < 300 ) { 8 | return false; 9 | } 10 | 11 | code.innerHTML = code.innerHTML.replace( ':', ' [Rozwiń]:' ); 12 | code.getElementsByTagName( 'span' )[ 0 ].toSwitch = next; 13 | } ); 14 | 15 | document.addEventListener( 'click', function( evt ) { 16 | if ( !evt.target || !evt.target.classList.contains( 'code-switch' ) ) { 17 | return true; 18 | } 19 | 20 | evt.preventDefault(); 21 | 22 | var target = evt.target, 23 | next = target.toSwitch, 24 | html = target.innerHTML; 25 | 26 | if ( !next || next.tagName.toLowerCase() !== 'pre' ) { 27 | return false; 28 | } 29 | 30 | next.classList.toggle( 'expanded' ); 31 | target.innerHTML = ( html === '[Zwiń]' ? '[Rozwiń]' : '[Zwiń]' ); 32 | } ); 33 | } catch( err ) {} 34 | 35 | $( 'nav' ).on( 'affix.bs.affix', function() { 36 | $( '.content' ).removeClass( 'col-md-offset-0' ).addClass( 'col-md-offset-4' ); 37 | } ).on( 'affix-top.bs.affix affix-bottom.bs.affix', function() { 38 | $( '.content' ).removeClass( 'col-md-offset-4' ); 39 | } ).affix( { 40 | offset: { 41 | top: function() { 42 | return ( this.top = $( '.navbar-fixed-top' ).outerHeight( true ) + 10 ); 43 | }, 44 | bottom: function () { 45 | return ( this.bottom = $( '.site-footer' ).outerHeight( true ) + 10 ); 46 | } 47 | } 48 | } ); 49 | 50 | var isPrismNeeded = !!document.querySelector( 'pre > code' ); 51 | 52 | if ( isPrismNeeded ) { 53 | var script = document.createElement( 'script' ); 54 | script.src = '/js/prism.js'; 55 | document.body.appendChild( script ); 56 | 57 | var link = document.createElement( 'link' ); 58 | link.rel = 'stylesheet'; 59 | link.href = '/css/prism.css'; 60 | document.head.appendChild( link ); 61 | } 62 | 63 | var details = Array.prototype.slice.call( document.querySelectorAll( 'details' ) ); 64 | 65 | window.addEventListener( 'beforeprint', function() { 66 | details.forEach( function( detail ) { 67 | detail.setAttribute( 'data-open', detail.hasAttribute( 'open' ) ); 68 | detail.setAttribute( 'open', 'open' ); 69 | } ); 70 | } ); 71 | 72 | window.addEventListener( 'afterprint', function() { 73 | details.forEach( function( detail ) { 74 | var isOpen = detail.getAttribute( 'data-open' ) && detail.getAttribute( 'data-open' ) === 'true'; 75 | 76 | if ( !isOpen ) { 77 | detail.removeAttribute( 'open' ); 78 | } 79 | } ); 80 | } ); 81 | -------------------------------------------------------------------------------- /build/bs/js/main.js: -------------------------------------------------------------------------------- 1 | import "./jquery.js"; 2 | import "./bootstrap.js"; 3 | import "./custom.js"; 4 | -------------------------------------------------------------------------------- /build/bs/rollup.config.js: -------------------------------------------------------------------------------- 1 | import minify from 'rollup-plugin-babel-minify'; 2 | 3 | export default { 4 | input: `${__dirname}/js/main.js`, 5 | plugins: [ 6 | minify() 7 | ], 8 | output: { 9 | sourcemap: true, 10 | format: 'iife', 11 | file: `${__dirname}/../../public/js/main.js` 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /build/bs/scss/bootstrap-theme.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/) 3 | */ 4 | /*! 5 | * Bootstrap v3.4.1 (https://getbootstrap.com/) 6 | * Copyright 2011-2019 Twitter, Inc. 7 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 8 | */ 9 | .alert { 10 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 11 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 12 | } 13 | .alert-success { 14 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 15 | background-repeat: repeat-x; 16 | border-color: #b2dba1; 17 | } 18 | .alert-info { 19 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 20 | background-repeat: repeat-x; 21 | border-color: #9acfea; 22 | } 23 | .alert-warning { 24 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 25 | background-repeat: repeat-x; 26 | border-color: #f5e79e; 27 | } 28 | .alert-danger { 29 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 30 | background-repeat: repeat-x; 31 | border-color: #dca7a7; 32 | } 33 | -------------------------------------------------------------------------------- /build/bs/scss/custom.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | scroll-padding-top: 60px; 3 | } 4 | body { 5 | padding-top: 60px; 6 | color: #000; 7 | } 8 | @media print { 9 | body { 10 | padding-top: 0; 11 | } 12 | .cookiebanner { 13 | display: none !important; 14 | } 15 | } 16 | .navbar-brand { 17 | margin: 0; 18 | } 19 | .navbar-brand a { 20 | color: #9d9d9d; 21 | } 22 | .site-footer { 23 | margin-top: 50px; 24 | padding: 50px 0; 25 | background: #222; 26 | color: #9d9d9d; 27 | } 28 | .site-footer a { 29 | color: #fff; 30 | } 31 | .sidebar.affix { 32 | position: static; 33 | } 34 | @media screen and (min-width: 992px) { 35 | .sidebar.affix { 36 | position: fixed; 37 | } 38 | .sidebar.affix { 39 | left: 0; 40 | top: 60px; 41 | bottom: 0; 42 | } 43 | .sidebar.affix .sidebar-header { 44 | height: 40px; 45 | } 46 | .sidebar.affix .sidebar-inner { 47 | position: absolute; 48 | top: 80px; 49 | bottom: 0; 50 | left: 0; 51 | right: 0; 52 | overflow: auto; 53 | overscroll-behavior: contain; 54 | } 55 | } 56 | @media print { 57 | .sidebar { 58 | position: static !important; 59 | } 60 | } 61 | .header { 62 | overflow: hidden; 63 | page-break-after: avoid; 64 | break-after: avoid; 65 | } 66 | @media print { 67 | .header_main, .site-footer { 68 | padding: 0; 69 | margin: 0; 70 | height: 100vh; 71 | display: flex; 72 | justify-content: center; 73 | align-items: center; 74 | } 75 | .site-footer { 76 | page-break-before: always; 77 | break-before: always; 78 | } 79 | } 80 | .header__heading, .header__link { 81 | display: inline-block; 82 | margin-top: 0; 83 | } 84 | 85 | .header_main .header__link { 86 | font-size: 36px; 87 | margin-left: .5em; 88 | } 89 | @media print { 90 | .header__link { 91 | display: none; 92 | } 93 | } 94 | code { 95 | padding: 0; 96 | font-size: inherit; 97 | color: inherit; 98 | border-radius: 0; 99 | } 100 | .code-switch { 101 | cursor: pointer; 102 | } 103 | .code-switch { 104 | font-size: smaller; 105 | font-weight: bold; 106 | } 107 | pre[class*="language-"] { 108 | background-color: #f5f2f0; 109 | color: #000; 110 | max-height: 300px; 111 | overflow: auto; 112 | } 113 | pre.expanded { 114 | max-height: none; 115 | } 116 | @media print { 117 | .code-switch { 118 | display: none; 119 | } 120 | code[class*="language-"], pre[class*="language-"] { 121 | max-height: none; 122 | page-break-before: avoid; 123 | break-before: avoid; 124 | white-space: pre-wrap !important; 125 | word-break: break-all !important; 126 | } 127 | code[class*="language-"] *, pre[class*="language-"] * { 128 | color: #000 !important; 129 | opacity: 1 !important; 130 | } 131 | code[class*="language-"] a::after, pre[class*="language-"] a::after { 132 | content: ''; 133 | } 134 | } 135 | .code,.quote { 136 | margin-top: 5px; 137 | } 138 | @media print { 139 | #komentarze { 140 | display: none; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /build/bs/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | @import "bootstrap-theme"; 3 | @import "custom"; 4 | -------------------------------------------------------------------------------- /build/bs/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lista tutorialów 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 40 | 41 |
    42 |
    43 |

    Tutoriale Comandeera

    44 |

    Na tej stronie znajdziesz listę moich tutorialów i artykułów, które piszę od czasu do czasu.

    45 | 46 |

    Lista tutorialów

    47 |
    48 | {LIST} 49 |
    50 | 51 |

    Pozostałe artykuły

    52 |

    Tak, czasami pisuję gdzie indziej.

    53 |
      54 | {ARTS} 55 |
    56 |

    Dodatkowo jestem także redaktorem WebKrytyka, a wolnych chwilach prowadzę osobistego bloga.

    57 |
    58 |
    59 | 60 |
    61 |
    62 |

    Copyright © by .

    63 |
    64 |
    65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /build/bs/templates/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | {TITLE} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 45 | 46 |
    47 |
    48 |
    49 |

    {TITLE}

    50 | 51 | Wersja PDF 52 |
    53 |
    54 |
    55 | {NAV} 56 | 57 |
    58 | {CONTENT} 59 |
    60 |
    61 |
    62 | 63 |
    64 |
    65 |

    Copyright © by .

    66 |
    67 |
    68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /build/bs/templates/tutorial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | {TITLE} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 45 | 46 |
    47 |
    48 |
    49 |

    {TITLE}

    50 | 51 | Wersja PDF 52 |
    53 |
    54 |
    55 | {NAV} 56 | 57 |
    58 | {CONTENT} 59 |
    60 |
    61 |
    62 | 63 |
    64 |
    65 |

    Copyright © by .

    66 |
    67 |
    68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /build/generate-markdown.js: -------------------------------------------------------------------------------- 1 | const { join: joinPath } = require( 'path' ); 2 | const { readdirSync, readFileSync, writeFileSync } = require( 'fs' ); 3 | 4 | function convertToMD( txt ) { 5 | return txt. 6 | replace( /\[p\]/g, '\n' ). 7 | replace( /\[p=info\](.+?)\[\/p\]/g, '\n
    \n\n$1\n\n
    ' ). 8 | replace( /\[p=warning\](.+?)\[\/p\]/gs, '\n
    \n\n$1\n\n
    ' ). 9 | replace( /\[\/p\]/g, '' ). 10 | replace( /\[h([1-6])\="?(.+?)"?\](.+?)\[\/h\1\]/g, '\n$3' ). 11 | replace( /\[code=(.+?)\](.+?)\[\/code\]/gs, '\n```$1\n$2\n```' ). 12 | replace( /\[\/?tt\]/g, '`' ). 13 | replace( /\[q\](.+?)\[\/q\]/g, '$1'). 14 | replace( /\[b\](.+?)\[\/b\]/g, '$1'). 15 | replace( /\[i\](.+?)\[\/i\]/g, '$1'). 16 | replace( /\[url=(.+?)\](.+?)\[\/url\]/g, '[$2]($1)' ). 17 | replace( /\[\/?list\]/g, '\n' ). 18 | replace( /[\t\r ]*?\[\*\]/g, '*' ). 19 | replace( /\[spoiler="(.+?)"\](.+?)\[\/spoiler\]/gs, '
    $1\n$2\n
    ' ). 20 | replace( /\[quote="(.+?)"\](.+?)\[\/quote\]/gs, '
    \n$2\n
    $1
    ' ). 21 | replace( /\[quote\](.+?)\[\/quote\]/gs, '
    \n$1\n
    ' ). 22 | replace( /\[description\](.+?)\[\/description\]/g, '$1' ); 23 | } 24 | 25 | function convertDir( dir ) { 26 | const pages = readdirSync( joinPath( __dirname, dir ) ).filter( ( file ) => { 27 | return file.endsWith( '.tpl' ); 28 | } ); 29 | 30 | pages.forEach( ( page ) => { 31 | const fileName = page.replace( '.tpl', '.md' ); 32 | const content = readFileSync( joinPath( __dirname, dir, page ), 'utf-8' ); 33 | const newContent = convertToMD( content ); 34 | 35 | writeFileSync( joinPath( __dirname, dir, fileName ), newContent ); 36 | } ); 37 | } 38 | 39 | convertDir( 'tutorials' ); 40 | convertDir( 'pages' ); 41 | -------------------------------------------------------------------------------- /build/generate-pdf.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require( 'puppeteer' ); 2 | const { readdirSync } = require( 'fs' ); 3 | const { resolve: resolvePath } = require( 'path' ); 4 | const pages = readdirSync( resolvePath( __dirname, './tutorials' ) ).concat( readdirSync( resolvePath( __dirname, './pages' ) ) ); 5 | 6 | async function generatePDF( browser, pages ) { 7 | const promises = []; 8 | 9 | pages = pages.filter( ( page ) => { 10 | return page.endsWith( 'md' ); 11 | } ) 12 | console.log( pages ); 13 | pages.forEach( ( page ) => { 14 | const name = page.replace( '.md', '' ); 15 | 16 | promises.push( ( async() => { 17 | const page = await browser.newPage(); 18 | 19 | await page.goto( `http://localhost:8080/${ name }.html`, { 20 | waitUntil: 'load' 21 | } ); 22 | await page.pdf( { 23 | path: resolvePath( __dirname, `../public/${ name }.pdf` ), 24 | format: 'Letter', 25 | margin: { 26 | top: '2.50cm', 27 | left: '2.50cm', 28 | bottom: '2.50cm', 29 | right: '2.50cm' 30 | } 31 | } ); 32 | } )() ); 33 | } ); 34 | 35 | await Promise.all( promises ); 36 | } 37 | 38 | ( async() => { 39 | const browser = await puppeteer.launch(); 40 | 41 | try { 42 | await generatePDF( browser, pages ); 43 | } catch ( e ) { 44 | console.error( e ); 45 | } 46 | 47 | await browser.close(); 48 | } )(); 49 | -------------------------------------------------------------------------------- /build/pages/polityka-prywatnosci.md: -------------------------------------------------------------------------------- 1 | Polityka prywatności tutorialów Comandeera 2 | 3 |

    Polityka prywatności

    4 | 5 | 6 | Jeżeli tutaj trafiłeś, to niezawodny znak, że cenisz swoją prywatność. Doskonale to rozumiem, dlatego przygotowałem dla Ciebie ten dokument, w którym znajdziesz zasady przetwarzania danych osobowych oraz wykorzystywania plików cookies w związku z korzystaniem ze strony internetowej [https://tutorials.Comandeer.pl]("https://tutorials.comandeer.pl"). 7 | 8 | Informacja formalna na początek – administratorem strony jestem ja, Tomasz Jakut. 9 | 10 | W razie jakichkolwiek wątpliwości związanych z polityką prywatności, w każdej chwili możesz się ze mną skontaktować, wysyłając wiadomość na adres comandeer@comandeer.pl. 11 | 12 | 13 |

    Skrócona wersja – najważniejsze informacje

    14 | 15 | Dbam o Twoją prywatność, ale również o Twój czas. Dlatego przygotowałem dla Ciebie skróconą wersję najważniejszych zasad związanych z ochroną prywatności. 16 | 17 | 18 | * Kontaktując się ze mną, przekazujesz mi swoje dane osobowe, a ja gwarantuję Ci, że Twoje dane pozostaną poufne, bezpieczne i nie zostaną udostępnione jakimkolwiek podmiotom trzecim bez Twojej wyraźnej zgody. 19 | * Powierzam przetwarzanie danych osobowych tylko sprawdzonym i zaufanym podmiotom świadczącym usługi związane z przetwarzaniem danych osobowych. 20 | * Korzystam z narzędzi analitycznych Google Analytics, które zbierają informacje na temat Twoich odwiedzin strony, takie jak podstrony, które wyświetliłeś, czas, jaki spędziłeś na stronie czy przejścia pomiędzy poszczególnymi podstronami. W tym celu wykorzystywane są pliki cookies firmy Google LLC dotyczące usługi Google Analytics. 21 | * Osadzam na stronie nagrania wideo z serwisu YouTube. Gdy odtwarzasz takie nagrania, wykorzystywane są pliki cookies firmy Google LLC dotyczące usługi YouTube. 22 | * Zapewniam możliwość korzystania z funkcji społecznościowych, takich jak udostępnianie treści w serwisach społecznościowych oraz subskrypcja profilu społecznościowego. Korzystanie z tych funkcji wiąże się z wykorzystywaniem plików cookies administratorów serwisów społecznościowych takich jak Facebook, Instagram, YouTube, Twitter, Google+, LinkedIN. 23 | * Wykorzystuję pliki cookies własne w celu prawidłowego działania strony. 24 | 25 | 26 | 27 | Jeżeli powyższe informacje nie są dla Ciebie wystarczające, poniżej znajdziesz szczegóły. 28 | 29 | 30 |

    Dane osobowe

    31 | 32 | Administratorem Twoich danych osobowych w rozumieniu przepisów o ochronie danych osobowych jest Tomasz Jakut. 33 | 34 | Cele, podstawy prawne oraz okres przetwarzania danych osobowych wskazane są odrębnie w stosunku do każdego celu przetwarzania danych (patrz: opis poszczególnych celów przetwarzania danych osobowych poniżej). 35 | 36 | 37 |

    Uprawnienia

    38 | 39 | RODO przyznaje Ci następujące potencjalne uprawnienia związane z przetwarzaniem Twoich danych osobowych: 40 | [list=1] 41 | * prawo dostępu do danych osobowych, 42 | * prawo do sprostowania danych osobowych, 43 | * prawo do usunięcia danych osobowych, 44 | * prawo do ograniczenia przetwarzania danych osobowych, 45 | * prawo do wniesienia sprzeciwu co do przetwarzania danych osobowych, 46 | * prawo do przenoszenia danych, 47 | * prawo do wniesienia skargi do organu nadzorczego, 48 | * prawo do odwołania zgody na przetwarzanie danych osobowych, jeżeli takową zgodę wyraziłeś. 49 | 50 | 51 | 52 | Zasady związane z realizacją wskazanych uprawnień zostały opisane szczegółowo w art. 16–21 RODO. Zachęcam do zapoznania się z tymi przepisami. Ze swojej strony uważam za potrzebne wyjaśnić Ci, że wskazane powyżej uprawnienia nie są bezwzględne i nie będą przysługiwać Ci w stosunku do wszystkich czynności przetwarzania Twoich danych osobowych. Dla Twojej wygody dołożyłem starań, by w ramach opisu poszczególnych operacji przetwarzania danych osobowych wskazać na przysługujące Ci w ramach tych operacji uprawnienia. 53 | 54 | Podkreślam, że jedno z uprawnień wskazanych powyżej przysługuje Ci zawsze – jeżeli uznasz, że przy przetwarzaniu Twoich danych osobowych dopuściłem się naruszenia przepisów o ochronie danych osobowych, masz możliwość wniesienia skargi do organu nadzorczego (Prezesa Urzędu Ochrony Danych Osobowych). 55 | 56 | Zawsze możesz również zwrócić się do mnie z żądaniem udostępnienia Ci informacji o tym, jakie dane na Twój temat posiadamy oraz w jakich celach je przetwarzamy Wystarczy, że wyślesz wiadomość na adres comandeer@comandeer.pl. Dołożyłem jednak wszelkich starań, by interesujące Cię informacje zostały wyczerpująco przedstawione w niniejszej polityce prywatności. Podany powyżej adres e-mail możesz wykorzystać również w razie jakichkolwiek pytań związanych z przetwarzaniem Twoich danych osobowych. 57 | 58 | 59 |

    Bezpieczeństwo

    60 | 61 | Gwarantuję Ci poufność wszelkich przekazanych nam danych osobowych. Zapewniam podjęcie wszelkich środków bezpieczeństwa i ochrony danych osobowych wymaganych przez przepisy o ochronie danych osobowych. Dane osobowe są gromadzone z należytą starannością i odpowiednio chronione przed dostępem do nich przez osoby do tego nieupoważnione. 62 | 63 | 64 |

    Cele i czynności przetwarzania

    65 | 66 | 67 |

    Kontakt e-mailowy

    68 | 69 | Kontaktując się ze mną za pośrednictwem poczty elektronicznej, w tym również przesyłając zapytanie poprzez formularz kontaktowy, w sposób naturalny przekazujesz mi swój adres e-mail jako adres nadawcy wiadomości. Ponadto, w treści wiadomości możesz zawrzeć również inne dane osobowe. 70 | 71 | Twoje dane są w tym przypadku przetwarzane w celu kontaktu z Tobą, a podstawą przetwarzania jest art. 6 ust. 1 lit. a RODO, czyli Twoja zgoda wynikające z zainicjowania z nami kontaktu. Podstawą prawną przetwarzania po zakończeniu kontaktu jest usprawiedliwiony cel w postaci archiwizacji korespondencji na potrzeby wewnętrzne (art. 6 ust. 1 lit. c RODO). 72 | 73 | Treść korespondencji może podlegać archiwizacji i nie jestem w stanie jednoznacznie określić, kiedy zostanie usunięta. Masz prawo do domagania się przedstawienia historii korespondencji, jaką ze mną prowadziłeś (jeżeli podlegała archiwizacji), jak również domagać się jej usunięcia, chyba że jej archiwizacja jest uzasadniona z uwagi na moje nadrzędne interesy, np. obrona przed potencjalnymi roszczeniami z Twojej strony. 74 | 75 | 76 |

    Pliki cookies i inne technologie śledzące

    77 | 78 | Moja strona, podobnie jak niemal wszystkie inne strony internetowe, wykorzystuje pliki cookies, by zapewnić Ci najlepsze doświadczenia związane z korzystaniem z niej. 79 | 80 | Cookies to niewielkie informacje tekstowe, przechowywane na Twoim urządzeniu końcowym (np. komputerze, tablecie, smartfonie), które mogą być odczytywane przez nasz system teleinformatyczny. 81 | 82 | Cookies można podzielić na cookies własne oraz cookie podmiotów trzecich. 83 | 84 | Więcej szczegółów znajdziesz poniżej. 85 | 86 | 87 |

    Zgoda na cookies

    88 | 89 | Podczas pierwszej wizyty na stronie wyświetlana jest Ci informacja na temat stosowania plików cookies wraz z pytaniem o zgodę na wykorzystywanie plików cookies. Zawsze możesz zmienić ustawienia cookies z poziomu swojej przeglądarki albo w ogóle usunąć pliki cookies. Pamiętaj jednak, że wyłączenie plików cookies może powodować trudności w korzystaniu ze strony, jak również z wielu innych stron internetowych, które stosują cookies. 90 | 91 | 92 |

    Cookies własne

    93 | 94 | Cookies własne wykorzystuję w celu zapewnienia prawidłowego działania strony, w szczególności w celu dostosowania layoutu strony do Twoich preferencji. 95 | 96 | 97 |

    Cookies podmiotów trzecich

    98 | 99 | Moja strona, podobnie jak większość współczesnych stron internetowych, wykorzystuje funkcje zapewniane przez podmioty trzecie, co wiąże się z wykorzystywaniem plików cookies pochodzących od podmiotów trzecich. Wykorzystanie tego rodzaju plików cookies zostało opisane poniżej. 100 | 101 | 102 |

    Analiza i statystyka

    103 | 104 | Wykorzystuję cookies do śledzenia statystyk strony, takich jak liczba osób odwiedzających, rodzaj systemu operacyjnego i przeglądarki internetowej wykorzystywanej do przeglądania strony, czas spędzony na stronie, odwiedzone podstrony etc. Korzystam w tym zakresie z Google Analytics, co wiąże się z wykorzystaniem plików cookies firmy Google LLC. 105 | 106 | 107 |

    Narzędzia społecznościowe

    108 | 109 | Zapewniam możliwość korzystania z funkcji społecznościowych, takich jak udostępnianie treści w serwisach społecznościowych oraz subskrypcja profilu społecznościowego. Korzystanie z tych funkcji wiąże się z wykorzystywaniem plików cookies administratorów serwisów społecznościowych takich jak Facebook, Instagram, YouTube, Twitter, Google+, LinkedIN. 110 | 111 | Osadzam na stronie nagrania wideo z serwisu YouTube. Gdy odtwarzasz takie nagrania, wykorzystywane są pliki cookies firmy Google LLC dotyczące usługi YouTube. 112 | -------------------------------------------------------------------------------- /build/pages/polityka-prywatnosci.tpl: -------------------------------------------------------------------------------- 1 | [description]Polityka prywatności tutorialów Comandeera[/description] 2 | [h1="start"]Polityka prywatności[/h1] 3 | 4 | [p]Jeżeli tutaj trafiłeś, to niezawodny znak, że cenisz swoją prywatność. Doskonale to rozumiem, dlatego przygotowałem dla Ciebie ten dokument, w którym znajdziesz zasady przetwarzania danych osobowych oraz wykorzystywania plików cookies w związku z korzystaniem ze strony internetowej [url="https://tutorials.comandeer.pl"]https://tutorials.Comandeer.pl[/url].[/p] 5 | [p] Informacja formalna na początek – administratorem strony jestem ja, Tomasz Jakut.[/p] 6 | [p]W razie jakichkolwiek wątpliwości związanych z polityką prywatności, w każdej chwili możesz się ze mną skontaktować, wysyłając wiadomość na adres comandeer@comandeer.pl.[/p] 7 | 8 | [h2="skrocona-wersja-najwazniejsze-informacje"]Skrócona wersja – najważniejsze informacje[/h2] 9 | [p]Dbam o Twoją prywatność, ale również o Twój czas. Dlatego przygotowałem dla Ciebie skróconą wersję najważniejszych zasad związanych z ochroną prywatności.[/p] 10 | [list] 11 | [*] Kontaktując się ze mną, przekazujesz mi swoje dane osobowe, a ja gwarantuję Ci, że Twoje dane pozostaną poufne, bezpieczne i nie zostaną udostępnione jakimkolwiek podmiotom trzecim bez Twojej wyraźnej zgody. 12 | [*] Powierzam przetwarzanie danych osobowych tylko sprawdzonym i zaufanym podmiotom świadczącym usługi związane z przetwarzaniem danych osobowych. 13 | [*] Korzystam z narzędzi analitycznych Google Analytics, które zbierają informacje na temat Twoich odwiedzin strony, takie jak podstrony, które wyświetliłeś, czas, jaki spędziłeś na stronie czy przejścia pomiędzy poszczególnymi podstronami. W tym celu wykorzystywane są pliki cookies firmy Google LLC dotyczące usługi Google Analytics. 14 | [*] Osadzam na stronie nagrania wideo z serwisu YouTube. Gdy odtwarzasz takie nagrania, wykorzystywane są pliki cookies firmy Google LLC dotyczące usługi YouTube. 15 | [*] Zapewniam możliwość korzystania z funkcji społecznościowych, takich jak udostępnianie treści w serwisach społecznościowych oraz subskrypcja profilu społecznościowego. Korzystanie z tych funkcji wiąże się z wykorzystywaniem plików cookies administratorów serwisów społecznościowych takich jak Facebook, Instagram, YouTube, Twitter, Google+, LinkedIN. 16 | [*] Wykorzystuję pliki cookies własne w celu prawidłowego działania strony. 17 | [/list] 18 | [p]Jeżeli powyższe informacje nie są dla Ciebie wystarczające, poniżej znajdziesz szczegóły.[/p] 19 | 20 | [h2="dane-osobowe"]Dane osobowe[/h2] 21 | [p][b]Administratorem[/b] Twoich danych osobowych w rozumieniu przepisów o ochronie danych osobowych jest Tomasz Jakut.[/p] 22 | [p][b]Cele, podstawy prawne oraz okres przetwarzania danych osobowych[/b] wskazane są odrębnie w stosunku do każdego celu przetwarzania danych (patrz: opis poszczególnych celów przetwarzania danych osobowych poniżej).[/p] 23 | 24 | [h3="uprawnienia"]Uprawnienia[/h3] 25 | [p]RODO przyznaje Ci następujące potencjalne uprawnienia związane z przetwarzaniem Twoich danych osobowych:[/p] 26 | [list=1] 27 | [*] prawo dostępu do danych osobowych, 28 | [*] prawo do sprostowania danych osobowych, 29 | [*] prawo do usunięcia danych osobowych, 30 | [*] prawo do ograniczenia przetwarzania danych osobowych, 31 | [*] prawo do wniesienia sprzeciwu co do przetwarzania danych osobowych, 32 | [*] prawo do przenoszenia danych, 33 | [*] prawo do wniesienia skargi do organu nadzorczego, 34 | [*] prawo do odwołania zgody na przetwarzanie danych osobowych, jeżeli takową zgodę wyraziłeś. 35 | [/list] 36 | [p]Zasady związane z realizacją wskazanych uprawnień zostały opisane szczegółowo w art. 16–21 RODO. Zachęcam do zapoznania się z tymi przepisami. Ze swojej strony uważam za potrzebne wyjaśnić Ci, że wskazane powyżej uprawnienia nie są bezwzględne i nie będą przysługiwać Ci w stosunku do wszystkich czynności przetwarzania Twoich danych osobowych. Dla Twojej wygody dołożyłem starań, by w ramach opisu poszczególnych operacji przetwarzania danych osobowych wskazać na przysługujące Ci w ramach tych operacji uprawnienia.[/p] 37 | [p]Podkreślam, że jedno z uprawnień wskazanych powyżej przysługuje Ci zawsze – jeżeli uznasz, że przy przetwarzaniu Twoich danych osobowych dopuściłem się naruszenia przepisów o ochronie danych osobowych, masz możliwość wniesienia skargi do organu nadzorczego (Prezesa Urzędu Ochrony Danych Osobowych).[/p] 38 | [p]Zawsze możesz również zwrócić się do mnie z żądaniem udostępnienia Ci informacji o tym, jakie dane na Twój temat posiadamy oraz w jakich celach je przetwarzamy Wystarczy, że wyślesz wiadomość na adres comandeer@comandeer.pl. Dołożyłem jednak wszelkich starań, by interesujące Cię informacje zostały wyczerpująco przedstawione w niniejszej polityce prywatności. Podany powyżej adres e-mail możesz wykorzystać również w razie jakichkolwiek pytań związanych z przetwarzaniem Twoich danych osobowych.[/p] 39 | 40 | [h3="bezpieczenstwo"]Bezpieczeństwo[/h3] 41 | [p]Gwarantuję Ci poufność wszelkich przekazanych nam danych osobowych. Zapewniam podjęcie wszelkich środków bezpieczeństwa i ochrony danych osobowych wymaganych przez przepisy o ochronie danych osobowych. Dane osobowe są gromadzone z należytą starannością i odpowiednio chronione przed dostępem do nich przez osoby do tego nieupoważnione.[/p] 42 | 43 | [h2="cele-i-czynnosci-przetwarzania"]Cele i czynności przetwarzania[/h2] 44 | 45 | [h3="kontakt-e-mailowy"]Kontakt e-mailowy[/h3] 46 | [p]Kontaktując się ze mną za pośrednictwem poczty elektronicznej, w tym również przesyłając zapytanie poprzez formularz kontaktowy, w sposób naturalny przekazujesz mi swój adres e-mail jako adres nadawcy wiadomości. Ponadto, w treści wiadomości możesz zawrzeć również inne dane osobowe.[/p] 47 | [p]Twoje dane są w tym przypadku przetwarzane w celu kontaktu z Tobą, a podstawą przetwarzania jest art. 6 ust. 1 lit. a RODO, czyli Twoja zgoda wynikające z zainicjowania z nami kontaktu. Podstawą prawną przetwarzania po zakończeniu kontaktu jest usprawiedliwiony cel w postaci archiwizacji korespondencji na potrzeby wewnętrzne (art. 6 ust. 1 lit. c RODO).[/p] 48 | [p]Treść korespondencji może podlegać archiwizacji i nie jestem w stanie jednoznacznie określić, kiedy zostanie usunięta. Masz prawo do domagania się przedstawienia historii korespondencji, jaką ze mną prowadziłeś (jeżeli podlegała archiwizacji), jak również domagać się jej usunięcia, chyba że jej archiwizacja jest uzasadniona z uwagi na moje nadrzędne interesy, np. obrona przed potencjalnymi roszczeniami z Twojej strony.[/p] 49 | 50 | [h2="pliki-cookies-i-inne-technologie-sledzace"]Pliki cookies i inne technologie śledzące[/h2] 51 | [p]Moja strona, podobnie jak niemal wszystkie inne strony internetowe, wykorzystuje pliki cookies, by zapewnić Ci najlepsze doświadczenia związane z korzystaniem z niej.[/p] 52 | [p]Cookies to niewielkie informacje tekstowe, przechowywane na Twoim urządzeniu końcowym (np. komputerze, tablecie, smartfonie), które mogą być odczytywane przez nasz system teleinformatyczny.[/p] 53 | [p]Cookies można podzielić na cookies własne oraz cookie podmiotów trzecich.[/p] 54 | [p]Więcej szczegółów znajdziesz poniżej.[/p] 55 | 56 | [h3="zgoda-na-cookies"]Zgoda na cookies[/h3] 57 | [p]Podczas pierwszej wizyty na stronie wyświetlana jest Ci informacja na temat stosowania plików cookies wraz z pytaniem o zgodę na wykorzystywanie plików cookies. Zawsze możesz zmienić ustawienia cookies z poziomu swojej przeglądarki albo w ogóle usunąć pliki cookies. Pamiętaj jednak, że wyłączenie plików cookies może powodować trudności w korzystaniu ze strony, jak również z wielu innych stron internetowych, które stosują cookies.[/p] 58 | 59 | [h3="cookies-wlasne"]Cookies własne[/h3] 60 | [p]Cookies własne wykorzystuję w celu zapewnienia prawidłowego działania strony, w szczególności w celu dostosowania layoutu strony do Twoich preferencji.[/p] 61 | 62 | [h3="cookies-podmiotow-trzecich"]Cookies podmiotów trzecich[/h3] 63 | [p]Moja strona, podobnie jak większość współczesnych stron internetowych, wykorzystuje funkcje zapewniane przez podmioty trzecie, co wiąże się z wykorzystywaniem plików cookies pochodzących od podmiotów trzecich. Wykorzystanie tego rodzaju plików cookies zostało opisane poniżej.[/p] 64 | 65 | [h4="analiza-i-statystyka"]Analiza i statystyka[/h4] 66 | [p]Wykorzystuję cookies do śledzenia statystyk strony, takich jak liczba osób odwiedzających, rodzaj systemu operacyjnego i przeglądarki internetowej wykorzystywanej do przeglądania strony, czas spędzony na stronie, odwiedzone podstrony etc. Korzystam w tym zakresie z Google Analytics, co wiąże się z wykorzystaniem plików cookies firmy Google LLC.[/p] 67 | 68 | [h4="narzedzia-spolecznosciowe"]Narzędzia społecznościowe[/h4] 69 | [p]Zapewniam możliwość korzystania z funkcji społecznościowych, takich jak udostępnianie treści w serwisach społecznościowych oraz subskrypcja profilu społecznościowego. Korzystanie z tych funkcji wiąże się z wykorzystywaniem plików cookies administratorów serwisów społecznościowych takich jak Facebook, Instagram, YouTube, Twitter, Google+, LinkedIN.[/p] 70 | [p]Osadzam na stronie nagrania wideo z serwisu YouTube. Gdy odtwarzasz takie nagrania, wykorzystywane są pliki cookies firmy Google LLC dotyczące usługi YouTube.[/p] 71 | -------------------------------------------------------------------------------- /build/pageslist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'Polityka prywatności': 'polityka-prywatnosci' 3 | }; 4 | -------------------------------------------------------------------------------- /build/tutorials/js-beauty.md: -------------------------------------------------------------------------------- 1 | Kilka porad, aby Twój kod JS był jeszcze lepszy niż obecnie! 2 | 3 |

    Wypieść swój JS

    4 | 5 | 6 |
    7 | 8 | Ten tutorial jest już niestety dość przestarzały i opisuje starszą wersję standardu JS (ECMAScript 5) a także niektóre praktyki, których obecnie nawet ja nie uznaję już za najlepsze. Dlatego lepiej potraktuj go wyłącznie jako ciekawostkę historyczną… i poczekaj aż napiszę o JS coś równie sensownego. 9 | 10 |
    11 | 12 | 13 |

    Inteligentny parser

    14 | 15 | JS to język, który pozwala chyba na największą dowolność w składni ze wszystkich języków programowania. Ba, jego parser jest tak sprawny, że sam sobie wstawia średniki w miejscach, w których być powinny. 16 | 17 | ```javascript 18 | return 19 | {}; 20 | ``` 21 | 22 | Ten krótki kod – mimo że na pierwszy, a nawet drugi rzut oka wydaje się poprawny – rzuci nam ładny błąd na konsolę. Czemu? Bo parser widzi go tak: 23 | 24 | ```javascript 25 | return; //bo niedbały programista zapomniał go tu dać 26 | {}; //o, a co tu robi definicja obiektu? 27 | ``` 28 | 29 | Przez to małe głupstwo kiedyś zraziłem się do JSON i za wszelką cenę próbowałem go omijać. Dlatego na sam początek porad ot, taki mały kwiatek z własnego doświadczenia. 30 | 31 | 32 |

    Krótszy zapis

    33 | 34 | ```javascript 35 | var width = 0; 36 | var height = 0; 37 | var img = null; 38 | var elem = null; 39 | ``` 40 | 41 | vs. 42 | 43 | ```javascript 44 | var width = 0 45 | ,height = 0 46 | ,img = null 47 | ,elem = null; 48 | ``` 49 | 50 | IMO czytelniej. Gdyby ktoś pytał czemu przecinek jest przerzucony na początek nowej linii, a nie zostawiony na końcu poprzedniej: spróbuj pousuwać parę zmiennych z listy. Jeśli przecinek jest na końcu linijki, musisz usunąć interesującą Cię zmienną, a następnie przecinek. Tak usuwasz jedno i drugie za jednym zamachem i na pewno nie zostawisz przecinka przez nieuwagę. 51 | 52 | 53 |
    54 | 55 | Od kiedy ten tutorial powstał, jednak powróciłem do "normalnego" sposobu zapisu przecinków. Mój styl można podejrzeć [na moim GitHubie](https://github.com/Comandeer), a zwłaszcza [w projekcie BEMQuery](https://github.com/BEMQuery/bemquery-package-boilerplate). 56 | 57 |
    58 | 59 | 60 |

    Strict mode

    61 | 62 | To małe cudeńko bardzo ułatwia życie, utrudniając je. Włączenie tzw. "strict mode" (tryb ścisły jak ktoś polski lubi) usuwa najbardziej bugowate części JS (np. `with` czy też ciut naprawia stringi w `setTimeout`). Co więcej, nie pozwala nam tworzyć nieświadomie zmiennych globalnych, np tak: 63 | 64 | ```javascript 65 | for(i = 0; iLiterały 88 | 89 | Spójrzmy na ten kod: 90 | 91 | ```javascript 92 | var a = new Array(1, 2, 3) 93 | ,b = new Object(); 94 | 95 | b.a = 1; 96 | b.b = 2; 97 | 98 | for(var i = 0; isetTimeout 144 | 145 | Nie przekazuj nazwy funkcji jako stringa! 146 | 147 | ```javascript 148 | setTimeout("funkcja()", 1000); //don't do this! 149 | ``` 150 | 151 | Tym samym wywołujesz sobie `eval`, a jak każdy wie – `eval` jest [s]złe[/s] niepotrzebnie wykorzystywane, co jedynie obniża wydajność! Przekaż uchwyt do funkcji: 152 | 153 | ```javascript 154 | setTimeout(funkcja, 1000); 155 | ``` 156 | 157 | A jak już musisz parametry przekazać: 158 | 159 | ```javascript 160 | setTimeout(function(){funkcja(1, 2);}, 1000); 161 | //lub 162 | setTimeout(funkcja, 1000, 1, 2); 163 | ``` 164 | 165 | 166 |

    Pętle i obiekty

    167 | 168 | Masz obiekt i musisz po nim poiterować? Zapewne robisz coś takiego: 169 | 170 | ```javascript 171 | var o = { 172 | a: 1 173 | ,b: 2 174 | ,c: 3 175 | }; 176 | 177 | for(var x in o) 178 | { 179 | console.log(o[x]); 180 | } 181 | ``` 182 | 183 | Zgadłem? No to źle robisz: 184 | 185 | ```javascript 186 | Object.prototype.oops = 'BUGAHA!'; 187 | 188 | var o = { 189 | a: 1 190 | ,b: 2 191 | ,c: 3 192 | }; 193 | 194 | for(var x in o) 195 | { 196 | console.log(o[x]); 197 | } 198 | ``` 199 | 200 | Powyższy kod wyświetli nam także 'BUGAHA!' (bo `for..in` iteruje także po wszystkich nienatywnych rozszerzeniach prototypu `Object`). Nie tego chcemy, prawda? A wystarczy dodać jedną linijkę: 201 | 202 | ```javascript 203 | Object.prototype.oops = 'BUGAHA!'; 204 | 205 | var o = { 206 | a: 1 207 | ,b: 2 208 | ,c: 3 209 | }; 210 | 211 | for(var x in o) 212 | { 213 | if(o.hasOwnProperty(x)) 214 | console.log(o[x]); 215 | } 216 | ``` 217 | 218 | I już. Metoda `hasOwnProperty` sprawdza czy wartość podana jako x na pewno jest częścią naszego obiektu i czy nie pochodzi z prototypu. 219 | 220 | Jest też inny sposób, aby zupełnie ominąć jakiekolwiek prototypy i nie martwić się o nie: 221 | 222 | ```javascript 223 | Object.prototype.oops = 'BUGAHA!'; 224 | 225 | var o = Object.create(null); 226 | o.a = 1; 227 | o.b = 2; 228 | o.c = 3; 229 | 230 | for(var x in o) 231 | { 232 | console.log(o[x]); 233 | } 234 | ``` 235 | 236 | `Object.create` tworzy nam obiekt z prototypu podanego jako pierwszy parametr, tak więc tworzymy obiekt z pustym prototypem (domyślnie jest to `Object.prototype`). W starszych przeglądarkach ten sposób nie działa. 237 | 238 | Jeszcze ładniej można to zrobić, korzystając z `Object.keys` (która to metoda nie szuka niczego w prototypach i zwraca wszystko w postaci normalnej tablicy kluczy): 239 | 240 | ```javascript 241 | var o = { 242 | a: 1 243 | ,b: 2 244 | ,c: 3 245 | }; 246 | 247 | Object.keys(o).forEach(function(x) 248 | { 249 | console.log(o[x]); 250 | }); 251 | ``` 252 | 253 | 254 |

    Funkcje natychmiastowego wywołania i przestrzenie nazw

    255 | 256 | Każdy doskonale wie, że zmienne globalne są bleeee. Jednak w wielu skryptach można znaleźć coś takiego: 257 | 258 | ```javascript 259 | var width = 0; 260 | var height = 0; 261 | var img = null; 262 | var elem = null; 263 | //itp. 264 | ``` 265 | 266 | Tym sposobem brudzimy sobie globalny scope! 267 | 268 | ```javascript 269 | console.log(window['width']); 270 | ``` 271 | 272 | A można lepiej, wykorzystując zasięg zmiennych: 273 | 274 | ```javascript 275 | (function() 276 | { 277 | var width = 0; 278 | var height = 0; 279 | var img = null; 280 | var elem = null; 281 | }()); 282 | console.log(window['width']); 283 | ``` 284 | 285 | OK, a jeśli chcemy coś specjalnie umieścić w globalnym scope, np. funkcje naszego super-hiper API? Oczywiście głupim pomysłem jest ładowanie oddzielnie wszystkich 150+ funkcji, bo istnieje szansa, że coś naszego nadpisze funkcje już używane na stronie (np. funkcja o nazwie `resizeImg`). Wtedy możemy posłużyć się przestrzenią nazw: 286 | 287 | ```javascript 288 | 289 | var API = { 290 | resizeImg: function() 291 | { 292 | console.log('wywołano'); 293 | } 294 | }; 295 | API.resizeImg(); 296 | 297 | ``` 298 | 299 | Po połączeniu obydwu metod możemy osiągnąć coś takiego 300 | 301 | ```javascript 302 | (function($) 303 | { 304 | var API = {} 305 | ,resizeImg = jakiswarunek ? function() {console.log('a');} : function() {console.log(b);}; 306 | 307 | API.resizeImg = resizeImg; 308 | $.API = API; 309 | }(window)) 310 | ``` 311 | 312 | Voila! W globalnym scope mamy tylko to, co chcieliśmy mieć! 313 | 314 | 315 |

    Feature detection

    316 |
    317 | Sniffing an user agent is like sniffing a glue 318 |
    porneL
    319 | 320 | Dlatego też, zamiast opierać się na wątpliwym przekonaniu, że w IE 5.5.1733958399 zainstalowanym pod Win XP z SP 14 na pewno to działa, warto sprawdzić czy naprawdę funkcja x istnieje i jest funkcją. Wyobraźmy sobie choćby, że chcemy stworzyć obiekt przy pomocy `Object.create`, ale nie jesteśmy pewni czy ta metoda istnieje: 321 | 322 | ```javascript 323 | var o = Object.create(null); 324 | ``` 325 | 326 | W starszych IE wywali nam ładny ReferrenceError. A wystarczy sprawdzić czy ta metoda istnieje: 327 | 328 | ```javascript 329 | if(typeof Object.create === 'function') 330 | ``` 331 | 332 | Jeśli nie, to można walnąć [polyfilla](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create#Polyfill). Na tej samej zasadzie działa cała biblioteka [Modernizr](http://modernizr.com). 333 | 334 | 335 |

    Event delegation

    336 | 337 | Zdarzenia bąbelkują (jak ktoś nie wierzy, to niech [poczyta](http://www.quirksmode.org/js/events_order.html)). Dlatego też możliwe staje się odkrycie, że jakiś tam akapit w jakimś tam divie został kliknięty. 338 | 339 | ```javascript 340 | (function(d) 341 | { 342 | d.getElementsByTagName('div')[0].addEventListener('click', function(e) 343 | { 344 | var t = e.target; 345 | if(t.tagName.toLowerCase() === 'p') 346 | console.log(t.innerHTML); 347 | }, false); 348 | }(document)); 349 | ``` 350 | 351 | Kiedy warto tego użyć? Jeśli np. mamy dużo przycisków na stronie i wszystkie są w jednym rodzicu. Zamiast przypisywać zdarzenia do każdego z nich, można przypisać te zdarzenia do ich rodzica i za pomocą `e.target` (w IE `e.srcElement`) sprawdzić, co tak naprawdę zostało kliknięte. Przydaje się także przy stronach ajaksowych, gdzie część elementów interaktywnych zostanie dodana po wczytaniu strony. Wtedy można doczepić zdarzenie np. do `body` i mieć pewność, że każdy przycisk będzie klikalny. 352 | -------------------------------------------------------------------------------- /build/tutorials/js-hiding.md: -------------------------------------------------------------------------------- 1 | Ciekawy trick zaczerpnięty z archiwów Opery. 2 | 3 |

    [JavaScript] Ukrywanie ciała funkcji

    4 | 5 | 6 |
    7 | 8 | Niestety, w nowych wersjach Chrome ta metoda już nie działa i dostajemy pełen dostęp do ciała funkcji. 9 | 10 |
    11 | 12 |
    13 | 14 | Na wstępie uprzedzę, że technika ta nie powstała w mym płodnym umyśle. Została znaleziona przez [developera Opery](http://web.archive.org/web/20130907212310/http://my.opera.com/hallvors/blog/2012/03/20/debugging-maps-google-maps) w czasie debugowania pewnego upierdliwego błędu w działaniu Google Maps w tejże przeglądarce. Jak nietrudno zgadnąć, zostało to w bólach znalezione w źródle tego zaawansowanego webappa. Ja jedynie postanowiłem to wykorzystać na szerszą skalę. Trzeba też zaznaczyć, że technika ta jest średnio przydatna jeśli nie zaciemnimy równocześnie źródła skryptu (np. poprzez uglify.js czy też GCC). 15 | 16 |
    17 | 18 | O co wgl chodzi? A no chodzi o pewną magiczną właściwość poniższego fragmentu kodu: 19 | 20 | ```javascript 21 | Function.prototype 22 | ``` 23 | 24 | Niby co w tym takiego nadzwyczajnego? Już mówię! 25 | 26 | ```javascript 27 | console.log( Function.prototype.call.apply( Function.prototype.bind, [ function(){} ] ) ); 28 | ``` 29 | 30 | Zapewne w większości powyższy kod wzbudza podświadomy lęk. Przyznać trzeba, że naprawdę wygląda dość dziwacznie. Ale postaram się troszkę przybliżyć jego sens. 31 | 32 | Zacznijmy od `Function.prototype`. Jak już kiedyś wspomniałem, JS jest takim mocno zboczonym językiem, gdzie wszystko jest obiektem. Nic zatem dziwnego, że funkcje są tak naprawdę… obiektami "klasy" `Function` ("klasy", bo jak wiadomo ECMAScript to jedyna grupa języków prototypowanych). Zatem każda zmiana w prototypie tego globalnego obiektu będzie dziedziczona przez wszystkie funkcje. Jednak tutaj skrypterzy Google poszli o krok dalej i wywołują poszczególne metody prosto z prototypu (czasami, ale to bardzo rzadko, okazuje się to przydatne)! 33 | 34 | Zatem jakie metody się tu wywołuje? `call`, `apply` i `bind`. Wszystkie działają dość podobnie i ich głównym zadaniem jest zmienienie kontekstu wywołania funkcji (`this`). Już wyjaśniam na przykładzie: 35 | 36 | ```javascript 37 | var d = { n: 1 }; 38 | 39 | function g() { 40 | console.log( this ); 41 | } 42 | 43 | g.apply( d ); 44 | ``` 45 | 46 | To wywołanie funkcji wyrzuci do konsoli obiekt `d` zamiast standardowego `window`.Dość użyteczne, pod warunkiem, że pisze się obiektowy JS i trochę kojarzy pojęcie kontekstu wywołania. Dla nas jednak nie jest to aż tak ważne. Wystarczy wiedzieć, że `apply` przyjmuje jako pierwszy parametr obiekt, który ma zastąpić nam `this`, a drugim parametrem jest tablica parametrów wywoływanej funkcji. Przykład, bo brzmi to dziwnie: 47 | 48 | ```javascript 49 | function g( r ) { 50 | console.log( r ); 51 | }; 52 | 53 | g.apply( window, [ 'whatever' ] ); 54 | ``` 55 | 56 | W konsoli ujrzymy wspaniały napis `whatever`. `call` w zasadzie działa tak samo, ale zamiast tablicy parametrów, przyjmuje ich listę: 57 | 58 | ```javascript 59 | function g( r ) { 60 | console.log( r ); 61 | }; 62 | 63 | g.call( window, 'whatever' ); 64 | ``` 65 | 66 | Efekt identyczny jak w poprzednim przykładzie. Trochę inaczej działa `bind`, ponieważ nie wywołuje funkcji w podanym kontekście, a zwraca jej "klona", działającego w danym scope. Dla kompletności przykład, z wiadomym wynikiem: 67 | 68 | ```javascript 69 | function g( r ) { 70 | console.log( r ); 71 | }; 72 | ( g.bind( window, [ 'whatever' ] ) )(); 73 | ``` 74 | 75 | Zatem gdy trochę się wysili szare komórki, można dojść do pewnego uogólnienia, że w bardzo zawoalowany sposób po prostu wywołujemy `Function.prototype.bind`. 76 | 77 | OK, a teraz gwóźdź programu: co nam daje połączenie tych trzech metod? A no, przy dobrze zaciemnionym kodzie bardzo utrudnia podejrzenie działania naszych funkcji i ukrywa to przed debuggerami. Kod 78 | 79 | ```javascript 80 | console.log( Function.prototype.call.apply( Function.prototype.bind, [ function() {} ] ) ); 81 | ``` 82 | 83 | zwróci nam bowiem nic nie mówiące 84 | 85 | ```javascript 86 | function() { [native code] } 87 | ``` 88 | 89 | Chyba nie muszę mówić jaką konsternację na twarzy młodocianego hakiera zrobi informacja, że nasza super-hiper-tajna funkcja, w której zaimplementowaliśmy czekający na opatentowanie algorytm, uparcie twierdzi, że jest funkcją wbudowaną i to na dodatek bez nazwy! 90 | 91 | I zanim polecisz opakowywać wszystkie swoje funkcje w ten sposób, wiadro zimnej wody dla ochłody: KOMPATYBILNOŚĆ. Tak, wiem, że wiesz, że ja wiem, że wiesz, że nie działa to w IE < 9 – przecież to logiczne! Ale niestety rynek tych przeglądarek wciąż jest dośc spory. Toteż – dla własnej wygody i kompatybilności – napiszmy sobie prostą funkcję pomocniczą. Nazwijmy ją (a jakże!) `obfuscate`. Użyjemy tu dwóch bardziej zaawansowanych elementów JS: funkcji natychmiastowego wywołania oraz closures (i w tym miejscu większość mniej zdeterminowanych postanowiła zamknąć tą kartę przeglądarki). Niech Was nie zwiodą pozory – wcale to (aż tak) trudne nie będzie! Na początku zadeklarujmy sobie swoją funkcję: 92 | 93 | ```javascript 94 | var obfuscate = ( function() { 95 | }() ); 96 | ``` 97 | 98 | To jest właśnie tzw. funkcja natychmiastowego wywołania (IIFE – Immediately Invoked Function Expression). Jak nietrudno zgadnąć, nazywa się tak, bo od razu po zadeklarowaniu jest wywoływana (to tak naprawdę jest wywołanie funkcji anonimowej). Na razie nasz kod jest bezużyteczny, bo zmienna `obfuscate` będzie miała wartość `undefined`. Toteż trzeba dodać do naszej funkcji `return`: 99 | 100 | ```javascript 101 | var obfuscate = ( function() { 102 | return function( func ) { 103 | return Function.prototype.call.apply( Function.prototype.bind, [ func ] ); 104 | }; 105 | }() ) 106 | ``` 107 | 108 | Tak, to jest właśnie closure – funkcja zwracana przez inną funkcję, zamknięta w jej obszarze (no ej, w końcu to technika od samego Google; nikt nie mówił, że łatwo będzie). Tak naprawdę, gdy JS się wczyta, przeglądarka otrzyma coś takiego: 109 | 110 | ```javascript 111 | var obfuscate = function( func ) { 112 | return Function.prototype.call.apply( Function.prototype.bind, [ func ] ); 113 | } 114 | ``` 115 | 116 | Czemu zatem nie zapisałem tego w taki sposób? Otóż przez IE 8 tak nie zapisałem! Trzeba przecież sprawdzić czy ta metoda ukrywania kodu jest wspierana i w zależności od tego funkcja `obfuscate` przyjąć może dwie postaci: 117 | 118 | 119 | * zaciemni nam kod, 120 | * zwróci niezaciemniony kod. 121 | 122 | 123 | 124 | A jak sprawdzić czy technika jest obsługiwana? Można sprawdzić czy przeglądarka wysypie się na wywołaniu zaciemnionej w taki sposób funkcji. Ok, ale to nam zatrzyma wykonywanie skryptu… chyba że wrzucimy do tego obsługę wyjątków! 125 | 126 | ```javascript 127 | try { 128 | Function.prototype.call.apply( Function.prototype.bind, [ function() {} ] )(); 129 | } catch ( err ) {} 130 | ``` 131 | 132 | Dobra, zwracanie niezaciemnionego kodu jest łatwe – wystarczy zwrócić parametr `func`. Tyle. Zatem po połączeniu wszystkiego razem, otrzymamy coś takiego: 133 | 134 | ```javascript 135 | var obfuscate = ( function() { 136 | var a = Function.prototype; 137 | 138 | try { 139 | a.call.apply( a.bind, [ function() {} ] )(); 140 | } catch ( err ) { 141 | return function( func ) { 142 | return func; 143 | }; 144 | } 145 | 146 | return function( func ) { 147 | return a.call.apply( a.bind, [ func ] ); 148 | }; 149 | }() ); 150 | ``` 151 | 152 | 153 | * Najpierw przypisujemy sobie `Function.prototype` do zmiennej `a` (żeby się później nie napisać i oszczędzić te parę bajtów). 154 | * Sprawdzamy, czy obfuskacja w ogóle działa w bloku `try`/`catch`. Jeśli nie, jak gdyby nigdy nic zwracamy funkcję zwracającą parametr `func` (nieźle to brzmi). 155 | * W innych przypadkach po prostu zwracamy funkcję zwracającą zaciemnioną funkcję 156 | 157 | 158 | 159 | Przykład: 160 | 161 | ```javascript 162 | var f = obfuscate( function( n, r ) { 163 | return n + ':' + r; 164 | } ); 165 | console.log( f( 'r', 't' ) ); // "r:t" 166 | console.log( f ); // function () { [native code] } 167 | ``` 168 | 169 | Demo online nie wstawiam, bo raczej za dynamiczne by nie było. 170 | 171 | Aaaaa, i taka uwaga na koniec: moja funkcja `obfuscate` bardzo lubi strict mode! 172 | -------------------------------------------------------------------------------- /build/tutorials/js-intl.md: -------------------------------------------------------------------------------- 1 | Słów kilka o standardzie ECMA-402. 2 | 3 |

    Data po polsku

    4 | 5 | Dzisiaj będzie krótko, rzeczowo i na temat (co dość rzadko mi się zdarza). Otóż: jak dobrze zrobić datę po polsku w JS i się przy tym nie narobić. 6 | 7 | 8 |

    Po staremu

    9 | 10 | Dotąd jedynym właściwym sposobem były różne dziwne kombinacje typu "stwórzmy sobie tablicę z polskimi nazwami miesięcy i dni tygodnia, a następnie podstawiajmy pod dane, zwracane przez `new Date`". Mogą one wyglądać [rozwlekle i odpychająco](http://webmade.org/porady/data-po-polsku-js.php) (a ich autorem nie jest Polak). Mogą być też [wzorowane na innych językach](http://phpjs.org/functions/strftime/), stając się jeszcze bardziej rozwlekłe i… nie JS-owe. Mogą w końcu zmienić się po prostu w wywołanie odpowiedniej, krótkiej funkcyjki w PHP, po stwierdzeniu, że JS się do tego nie nadaje. 11 | 12 | 13 |

    14 | 15 | `Intl` na ratunek! 16 | 17 |

    18 | 19 | Na szczęście się to zmieniło. I to nie znowu tak niedawno (bo w 2012). Wtedy powstał standard [ECMA-402 (ECMAScript Internationalization API)](http://www.ecma-international.org/ecma-402/1.0/). Oczywiście na odzew ze strony producentów przeglądarek trzeba było ciut poczekać, niemniej – jeśli wierzyć [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#Browser_Compatibility) – działa dziś wszędzie, oprócz Safari i – co prawdopodobnie może być o wiele ważniejsze – node.js (jednak od czego jest niezawodny GitHub i [polyfille](https://github.com/andyearnshaw/Intl.js); oczywiście w node.js/io.js [obsługę można sobie wkompilować](https://github.com/nodejs/io.js#intl-ecma-402-support)). 20 | 21 | Cały standard składa się z 3 głównych "klas", zamkniętych w krótkiej i schludnej przestrzeni nazw – `Intl`. Są to: 22 | 23 | 24 | * `Intl.Collator` – służący do porównywania tekstu 25 | * `Intl.NumberFormat` – służący do formatowania wszelkiej maści liczb (między innymi walut czy liczebników porządkowych) 26 | * `Intl.DateTimeFormat` – tak, tego właśnie użyjemy 27 | 28 | 29 | 30 | 31 |

    Formatujemy datę

    32 | 33 | Żeby wyświetlić ładną datę po polsku, musimy stworzyć nowy obiekt "klasy" `Intl.DateTimeFormat`. Jako pierwszy parametr konstruktor bierze nazwę języka (oczywiście zgodną ze standardem ISO, zatem dwuliterowy skrót): 34 | 35 | ```javascript 36 | var formatter = new Intl.DateTimeFormat( 'pl' ); 37 | ``` 38 | 39 | Już! Pierwszy krok za nami. Mamy obiekt wyświetlający datę po polsku. Udostępnia on aż jedną metodę – `format` – która formatuje przekazaną jej datę (jeśli jej nie podamy, zastosuje aktualną – przynajmniej w Chrome; polecam jednak ją podawać dla zwiększenia czytelności kodu). 40 | 41 | ```javascript 42 | formatter.format( new Date() ); // 23.7.2016 43 | ``` 44 | 45 | Jak widać, mało imponujące. Równie dobrze można to uznać za datę po angielsku. Na pomoc przychodzi nam drugi parametr konstruktora `Intl.DateTimeFormat` – jest to obiekt opcji. Przyjrzyjmy się takiemu przykładowi: 46 | 47 | ```javascript 48 | var formatter = new Intl.DateTimeFormat( 'pl', { 49 | day: 'numeric', 50 | month: 'long', 51 | year: 'numeric' 52 | } ); 53 | formatter.format( new Date() ); // 23 lipca 2016 54 | ``` 55 | 56 | Voila! To jest dokładnie to, o co nam chodziło. Oczywiście opcji jest więcej, po dokładny ich spis [zapraszam na MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat#Parameters). 57 | -------------------------------------------------------------------------------- /build/tutorials/js-jquery.md: -------------------------------------------------------------------------------- 1 | Pokazanie kilku alternatywnych ścieżek. 2 | 3 |

    Czemu nie potrzebujesz jQuery?

    4 | 5 | 6 |

    Mimo że zdaje ci się inaczej

    7 | 8 | Ten tutorial kieruję do wszystkich tych, którzy nie wyobrażają sobie wykonania jakiejkolwiek prostej czynności w JS bez użycia tej wszechstronnej biblioteki. Część zarzutów tyczy się też mnie, gdyż osobiście również korzystam z jQuery (hej, to nie moja wina, że DOM jest tak spaprany!) 9 | 10 | 11 | 12 |

    Alternatywa?

    13 | 14 | [VanillaJS](http://vanilla-js.com/) + [jeden z tych maluczkich](http://microjs.com/) 15 | 16 | 17 | 18 |

    Podstawowe operacje DOM-owe

    19 | 20 | 21 |

    Wyszukiwanie po selektorach

    22 | 23 | Potęgą jQuery jest jego silniczek Sizzle, który pozwala wyszukać dowolny element tylko i wyłącznie na podstawie selektora. To jest często główny/jedyny powód, dla którego część webmasterów sięga po jQ. 24 | 25 | ```javascript 26 | $( '#bardzo > optymalny .selektor #ktory .na-bank.dziala' ); 27 | ``` 28 | 29 |
    30 | 31 | Oczywiście optymalizacja takich selektorów to zupełnie inna sprawa. 32 | 33 |
    34 | 35 | Skupmy się na tym, czy w czystym JS istnieje jakaś alternatywa dla tego pięknego owijacza? Otóż tak! 36 | 37 | ```javascript 38 | document.querySelectorAll( '#bardzo > optymalny .selektor #ktory .na-bank.dziala' ); 39 | ``` 40 | 41 | Prawdę mówiąc, jeśli przyjrzymy się kodowi jQ, zauważymy, że wewnętrznie używa on właśnie tej metody. Nie trzeba chyba mówić, co jest szybsze. Oczywiście można pójść o krok dalej i zrobić sobie coś takiego: 42 | 43 | ```javascript 44 | var $ = document.querySelectorAll.bind( document ); 45 | ``` 46 | 47 | Voila! Mamy jQ bez jQ. 48 | 49 | 50 |

    Chainowanie metod

    51 | 52 | Chainowanie metod to to, co decyduje o niezwykłej użyteczności jQ w zastosowaniach DOM-owych. 53 | 54 | ```javascript 55 | $( '' ).attr( 'href', '#' ).on( 'click', function() {} ); 56 | ``` 57 | 58 | Owszem, w VanillaJS nie jestem w stanie uzyskać tak wygodnej obsługi DOM. Ale tu z pomocą przychodzi nam [Chainvas](http://leaverou.github.com/chainvas/) – małe cudeńko, które jest w stanie złańcuchować de facto wszystko. Dzięki temu można spokojnie napisać: 59 | 60 | ```javascript 61 | document.createElement( 'a' ).setAttribute( 'href', '#' ).addEventListener( 'click', function() {} ); 62 | ``` 63 | 64 | I tym sposobem odebraliśmy jQ największą jego przewagę w operacjach DOM-owych, oszczędzając przy tym 31KB kodu (oczywiście mowa tu o wersjach gzipniętych i zminifikowanych). 65 | 66 | 67 |

    Event delegation

    68 | 69 | w jQ jest prosto: 70 | 71 | ```javascript 72 | $( document ).on( 'click', '.clicky', function( evt ) { 73 | //awesome stuff 74 | } ); 75 | ``` 76 | 77 | w czystym JS też jest prosto 78 | 79 | ```javascript 80 | document.addEventListener( 'click', function( evt ) { 81 | if ( !evt.target || !e.target.matches( '.clicky' ) /* Można zmienić na !evt.target.classList.contains( 'clicky' )*/ ) { 82 | return; 83 | } 84 | //awesome stuff 85 | }, false ); 86 | ``` 87 | 88 | 89 |

    90 | 91 | `.clone` 92 | 93 |

    94 | 95 | Żeby sklonować obiekt w jQ, wystarczy skorzystać z metody `.clone`. Czy da się zrobić coś takiego w czystym JS? A i owszem. 96 | 97 | ```javascript 98 | function clone( obj ) { 99 | return obj.cloneNode ? obj.cloneNode( obj ) : JSON.parse( JSON.stringify( obj ) ); 100 | } 101 | console.log( clone( document.createElement( 'a' ) ) ); 102 | ``` 103 | 104 | Prosta i przyjemna sztuczka. 105 | 106 | 107 |

    108 | 109 | `.extend` 110 | 111 |

    112 | 113 | To już nie jest do końca DOM, ale może się tu nadać. 114 | 115 | Jak wiadomo, w jQ mamy metodę `.extend` rozszerzającą nam obiekt: 116 | 117 | ```javascript 118 | console.log( $.extend( {}, { cos: 1 } ) ); 119 | ``` 120 | 121 | W czystym JS [też się da](https://github.com/shimondoodkin/nodejs-clone-extend/blob/master/index.js#L40). Ba, można być nawet chamskim i po prostu wyciągnąć ten kod z jQ. 122 | 123 | 124 | 125 |

    Animacje

    126 | 127 | Co tu dużo mówić – od kiedy w CSS są `transition` i `animation`, większość rzeczy można w ogóle wyjąć poza JS. Natomiast niektóre inne animacje z jQ można równie łatwo napisać w czystym JS. Przykład, lekko poprawiony, ze strony VanillaJS: 128 | 129 | ```javascript 130 | $( '#thing' ).fadeOut(); 131 | //vs 132 | var s = document.getElementById( 'd' ).style; 133 | s.opacity = +( s.opacity || window.getComputedStyle( el )[ 'opacity' ] ); 134 | ( function f() { ( s.opacity -= 1 / 100 ) <= 0 ? s.display = "none" : requestAnimationFrame( f ) }() ); 135 | ``` 136 | 137 | Nie trzeba chyba zaznaczać, że porównanie objętościowe tych trzech linijek i całego jQ wypada korzystnie dla czystego JS. 138 | 139 | oczywiście warto zauważyć, że rAF jest tylko w nowszych browserach i trza by zarzucić jakimś fallbackiem: 140 | 141 | ```javascript 142 | window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function( f ){ setTimeout( f, 1000 / 60 ); }; 143 | ``` 144 | 145 | 146 | 147 |

    XHR

    148 | 149 | Tu nie będzie przykładów, jedynie krótkie info. 150 | 151 | Otóż wrapper w jQ jest cholernie wygodny i w ogóle. Jednak jeśli chcemy mieć dostęp do bardziej zaawansowanych własności obiektu xhr (np. `xhr.upload.progress`), [trzeba użyć natywnego obiektu](https://github.com/malsup/form/blob/master/jquery.form.js#L258). 152 | 153 | 154 | 155 |

    jQUI

    156 | 157 | Wiele rzeczy [da się napisać](http://pulpit.luke.co.pl/) bez tego, wystarcza trochę pomyślunku. 158 | 159 | Szybki przykład na resize elementów (akurat wykorzystywany w zupełnie innym kontekście niż browserowy): 160 | 161 | ```javascript 162 | if ( [ 'l', 'r' ].indexOf( resizing ) !== -1 ) { 163 | if ( resizing === 'l' ) { 164 | window.frame.width += x - e.pageX; 165 | window.frame.left -= x - e.pageX; 166 | } else { 167 | window.frame.width -= x - e.pageX; 168 | } 169 | 170 | if ( window.frame.width < 60 ) { 171 | window.frame.width = 60; 172 | } 173 | 174 | x = e.pageX; 175 | y = e.pageY; 176 | } else if ( [ 't', 'b' ].indexOf( resizing ) !== -1 ) { 177 | if ( resizing === 't' ) { 178 | window.frame.height += y - e.pageY; 179 | window.frame.top -= y - e.pageY; 180 | } else { 181 | window.frame.height -= y - e.pageY; 182 | } 183 | 184 | if ( window.frame.height < 120 ) { 185 | window.frame.height = 120; 186 | } 187 | 188 | x = e.pageX; 189 | y = e.pageY; 190 | } else { 191 | window.frame.width -= x-e.pageX; 192 | window.frame.height -= y-e.pageY; 193 | } 194 | ``` 195 | 196 | co prawda kod najwyższych lotów nie jest i raczej przedstawia ogólną ideę jak to może wyglądać, niż jak to powinno wyglądać. Ba, można zdać się na natywne funkcje, takie jak `resize` w CSS (miało działać na wszystkim, ale nie wiem czy ta obietnica jest spełniona) i drag&drop (w IE od wersji 5 bodaj!). Dobitnie to pokazuje, że nawet tak skomplikowane czynności jakie wykonuje za nas jQUI można zrobić zarówno bez niego, jak i jego ojca – jQ. 197 | 198 | 199 | 200 | 201 |

    Ale za to w jQ da się…

    202 | 203 | Z racji tego, że jQ jest tylko subsetem JS, śmiem twierdzić, że nie ma rzeczy, której nie dałoby się osiągnąć w czystym JS. Jestem w stanie podjąć każde wyzwanie i udowodnić, że się da bez! 204 | 205 | 206 |

    Czy porzucić?

    207 | 208 | Nie trzeba. Sam używam i jestem zadowolony. Cały problem polega na tym, że jQuery jest przedstawiane jako lekarstwo na wszelkie zło JS, a to po prostu wierutna bzdura. jQuery powstało jako helper DOM-owy i to wciąż jest jego główną siłą. 209 | 210 | Co więcej, jQuery niszczy świadomość programistów sieciowych. Coraz częściej można spotkać bowiem nie programistów JS, a programistów jQuery, którzy jedynie tą biblioteką są w stanie się posługiwać. 211 | 212 | Dlatego nie mówię, żeby przestać jQ używać. Jeśli natomiast do stronki, gdzie trza oskryptować jeden link, wsadzasz jQ, to znak, że coś jest nie tego. Jeśli natomiast umiesz zastąpić jQ [własnym rozwiązaniem](http://www.forumweb.pl/javascript/cancelbubble-stoppropagation-problem/397808#397808) i robisz to, gdy potrzebujesz jedną, konkretną rzecz, to oznacza, że jesteś zupełnie normalnym użytkownikiem tej biblioteki i syndrom uzależnienia Ciebie nie dotyczy. 213 | -------------------------------------------------------------------------------- /build/tutorials/polymer.md: -------------------------------------------------------------------------------- 1 | Przygody Comandeera z tym lepszym frameworkiem od Google. 2 | 3 |

    WebComponents i Polymer - niespełniony sen?

    4 | 5 | Nie tak dawno [zachwycałem się potęgą Web Components](http://webroad.pl/javascript/3505-web-components), wieszcząc im świetlaną przyszłość w webmasterskim świecie. Po kilku miesiącach używania tej technologii przyszedł czas na pierwsze refleksje i ostudzenie zbyt wcześnie zapalonego entuzjazmu. Trzeba posypać głowę popiołem i powiedzieć wprost: "To nie tak miało być". 6 | 7 | 8 |

    Założenia a rzeczywistość

    9 | 10 | Nie ukrywam, że w Web Components od samego początku upatrywałem nie tyle całkowitej zmiany paradygmatu tworzenia zaawansowanych aplikacji internetowych, co po prostu naturalnej i wymaganej obecnym stanem Sieci ewolucji. Ewolucji na polu tworzenia [responsywnych](http://www.reactivemanifesto.org/) interfejsów użytkownika. Tyle. Tylko tyle i aż tyle. 11 | 12 | Nigdy nie myślałem, żeby do znacznika móc włożyć logikę aplikacji, która nijak się ma do interfejsu… Okazało się jednak, że jestem w przytłaczającej mniejszości. Większość komponentów bowiem, które możemy znaleźć w Sieci, dotyczy rzeczy, które niegdyś były domeną potężnych frameworków MVW. [Deklaratywny router](http://component.kitchen/components/app-router), który wczytuje strony, będące… komponentami? Może i początkowo ta idea wydaje się interesująca, ale bardzo łatwo się przekonać jak bardzo strzelamy sobie w kolano, przenosząc lwią część logiki do HTML. To przerost formy nad treścią w swoim najdoskonalszym wydaniu - zamiast zrobić normalne linki i normalnie wczytywać strony z wykorzystaniem History API, tworzymy rozwiązanie problemu, który… sami na szybko stwarzamy. Tak, Web Components są fajne, ale to nie znaczy, że mamy wszystko na nie przepisać, tworząc rozwiązania niezwykle trudne do rozwijania i utrzymywania. Obawiam się jednak, że takie wykorzystanie Web Components będzie się stawać coraz bardziej popularne (wystarczy tutaj powiedzieć, że przecież Angular 2.0 będzie intensywnie korzystał z Polymera). Czy na pewno chcemy w pełni deklaratywnej Sieci? A jeśli tak, to dlaczego kiedyś protestowaliśmy przeciwko XHTML 2.0? 13 | 14 | 15 |

    Polymer - wrzód na tyłku

    16 | 17 | Nikt o zdrowych zmysłach nie tworzy aplikacji obecnie w "czystych" Web Components. Byłaby to sadomasochistyczna sztuka dla sztuki, ponieważ żadna przeglądarka (nawet bleeding edge Chrome) nie wspiera w pełni wszystkich specyfikacji wchodzących w skład Web Components. Dlatego też de facto wszyscy muszą używać jakichś polyfillów (profillów?). Nie będę ukrywał, że najpopularniejszym jest [projekt Polymer](https://www.polymer-project.org/), z którego korzystają także… inne polyfiille (takie jak Mozillowe [X-Tags](http://www.x-tags.org/) czy [Bosonic](http://bosonic.github.io/)). Mówiąc zatem krótko: na chwilę obecną tworzenie przy użyciu otwartego standardu, jakim są Web Components, sprowadza się do zamknięcia się w bibliotece serwowanej przez Google. Otwarta Sieć, czyż nie? 18 | 19 | Stosunek Google do ich projektów wszyscy znamy - w każdej chwili mogą je uwalić. Co więcej, Polymer jest na tyle specyficznym projektem, że - będąc "na rynku" już 2 lata - wciąż jest uznawany za skrajnie eksperymentalny. Co to oznacza? Że średnio co tydzień można obudzić się z ręką w nocniku, po nieuważnym wklupaniu `bower update` w konsoli. Tak, Polymer zmienia się na pniu. Ostatnio przeżyłem szok, gdy okazało się, że jego stare, dobre `platform.js` odeszło na emeryturę, zostając zastąpione bardziej modułowym `webcomponents.js`. Główna różnica? Zamiast dużego pliku, Polymer zasysa teraz kilkadziesiąt małych. Po prostu wydajność maksymalna out of box. Ale oczywiście inżynierowie Google problem widzą, dlatego przygotowali dla niego rozwiązanie (czy już wspominałem, że w świecie Web Components lubi się rozwiązywać problemy, które samemu się wcześniej stworzyło?) - [Vulcanizer](https://www.polymer-project.org/articles/concatenating-web-components.html). Oczywiście to rozwiązanie tymczasowe, bo tuż za rogiem czeka już HTTP 2.0 i czegoś takiego nie trzeba będzie używać… Tak samo, jak od dwóch lat nie powinniśmy używać dziwnego owijacza w formie Polymera. 20 | 21 | Częste zmiany to nie tylko sajgon w plikach, ale także sajgon przy testowaniu swoich komponentów - Polymer jest podzielony na tak szaloną ilość plików, że prawie zawsze występują jakieś nieporozumienia z cache. Nieporozumienia na tyle trudne, że aż byłem zmuszony [wyłączyć cache dla komponentów](https://github.com/Comandeer/dGUI/blob/18f8f1c08471bdc8c6b3a6f490203a1cc242bf89/components/.htaccess) - jak na razie nie wróży to dobrze Polymerowi, bo zasysanie go przy każdym żądaniu od nowa to czysta kpina. Dodajmy do tego konieczność importowania także samych [komponentów dGUI](http://dgui.comandeer.pl) i dostajemy co najmniej 3 sekundy do czasu pokazania czegokolwiek na stronie (bo Polymer łaskawie ukrywa wszystko, aż się komponenty nie doczytają…). O 2.5 sekundy za długo. 22 | 23 | I wreszcie - sama konwencja Polymera. [Każdy komponent](https://github.com/Comandeer/dGUI/blob/18f8f1c08471bdc8c6b3a6f490203a1cc242bf89/components/dgui-keyboard/dgui-keyboard.html) to style i JS wrzucone w znacznik `polymer-element`. Serio? Zważając na to jak specyficzne są projektowane zastosowania Web Components, uważam to co najmniej za dziwne (CSP, anyone?). Oczywiście Vulcanizer jest w stanie się tym zająć (czy już wspominałem o tworzeniu niepotrzebnych problemów?) - pytanie brzmi: czemu to nie jest domyślnym ustawieniem? Nagle z HTML-a zrobiono worek na wszystko. Witamy w XUL-u? 24 | 25 | Do tego dochodzi fakt, że całe środowisko Polymera opiera się na Bowerze. Zatem, żeby użyć Polymera muszę zainstalować node.js, zainstalować bowera przez npm i dopiero wówczas mogę zainstalować samego Polymera. Na tej samej zasadzie działają wszelkie komponenty w tym wielkim ekosystemie. Czy to plus, czy minus - to zależy. Czasami bardzo przydaje się ustandaryzowany sposób rozprowadzania zależności, innym razem jest to kula u nogi. 26 | 27 | I na sam koniec, komunikat z dev tools Firefoksa: 28 |
    29 | mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create 30 |
    Konsola JS
    31 | 32 | Nie oszukujmy się - żeby Web Components działały, wraz z emulacją Shadow DOM, gdzie np. trzeba zatrzymać leakage `[id]` na zewnątrz, Polymer musi nadpisać kilkadziesiąt prototypów DOM-owych. Takie zabawy nigdy nie były wydajne, a co więcej są narażone na wszelkie możliwe błędy (nie bez przyczyny istnieje złota zasada, że prototypów hosta się nie tyka). 33 | 34 | Oczywiście reszta punktów jest pisana z perspektywy właśnie Polymera - jedynej słusznej drogi ku Web Components. 35 | 36 | 37 |

    Szablony

    38 | 39 | Początkowo idea szablonów w DOM, tworzonych przy pomocy znacznika `template` brzmi sensownie. Ot, mamy wydzielone na zewnątrz drzewko DOM, na którym możemy sobie do woli operować i dopóki nie wsadzimy go na stronę, nic się nie dzieje - zdarzenia się w nim nie odpalają, obrazki nie wczytują… Do tego dostajemy do pracy z nim potężne narzędzia związane z trawersacją drzewka DOM. Precz z płaskimi stringami, bawimy się żywą strukturą! 40 | 41 | Problem polega na tym, że takie wykorzystanie szablonów jest skrajnie prymitywne i starcza dla podstawowych zastosowań. Dlatego w Polymerze wymyślili sobie tzw. "template bindings", które miały być zgłoszone do procesu standaryzacji w W3C, ale wygląda na to - całe szczęście - że tam nie dotarły. Na czym pomysł polega? Na tym, żeby móc używać w `template` uproszczonej składni [wąsów](http://mustache.github.io/) + logiki ukrytej skrzętnie w atrybutach elementów. Przykład prosto z dokumentacji: 42 | 43 | ```"markup" 44 | 49 | ``` 50 | 51 | Oczywiście to dodatkowo dostaje 2-way data binding. Brzmi fajnie, ale w praktyce to jest piekło na ziemi. Widać tu 3 poważne problemy: 52 | 53 | 54 | * z systemu DOM-owego niepostrzeżenie przeszliśmy z powrotem na parsowanie stringów. Tym samym nie ma najmniejszych powodów, żeby dalej korzystać z tagu `template` - odebrana nam zostaje jego główna zaleta: DOM. To pociąga za sobą bardzo poważne konsekwencje, związane z debugowaniem takiego kodu (nic niemówiące błędy o funkcjach anonimowych w funkcjach anonimowych, które jeszcze przechodzą przez `eval`; bardzo podobny problem dotyka wszystkie JS-owe parsery HTML-owych stringów, zatem Angulara, jak i JSX z Reacta). W tym wypadku lepiej skorzystać ze standardowych wąsów. 55 | * przenieśliśmy logikę na poziom HTML-a. Stare, przerabiane i za każdym razem ten pomysł upadał (patrz: Web Forms 2.0 i właśnie `[repeat]` pól). HTML nie służy do takich rzeczy - jest językiem opisu strony, nie językiem deklaratywnych szablonów. Od tego jest inny język (XSLT - i znów: czemu odrzuciliśmy XHTML 2.0, a teraz przepisujemy XML na HTML?) 56 | * 2-way data binding wprowadza więcej problemów niż je rozwiązuje. Bindowanie model → widok jest naturalne i de facto tak działają od zawsze wszystkie MVW. Bindowanie model ↔ widok już takie nie jest. Czy model musi odpowiadać na zmiany, które zachodzą w widoku? [To zależy](http://stackoverflow.com/questions/19481/is-data-binding-a-bad-idea). Niemniej wiązanie modelu bezpośrednio z DOM brzmi… podejrzanie. 57 | 58 | 59 | 60 | Polymer psuje to, co było dobre w `template`, sprowadzając go do poziomu rozwiązań, które istniały przed nim. A sens `template` leży w jego DOM-owej naturze, nie w logice wepchniętej w ledwo trzymające się atrybuty. 61 | 62 | 63 |

    ShadowDOM

    64 | 65 | ShadowDOM to HTML-owy sposób na enkapsulację. Tym samym tworzymy sobie czarne dziury - konkretny custom element ukrywa przed nami swoją implementację, udostępniając nam [ładne, eventowe API](https://github.com/Comandeer/dGUI/blob/18f8f1c08471bdc8c6b3a6f490203a1cc242bf89/components/dgui-colorpicker/demo.html#L17) i nic więcej. Fajna sprawa, jeśli potrzebujemy konkretnego elementu interfejsu, przy którym całkowicie nie interesuje nas jego wewnętrzny sposób działania, a tylko i wyłącznie to, co w zamian dostajemy. Colorpicker jest doskonałym tego przykładem (Po co mi, jako autorowi edytora graficznego, potrzebna jest wiedza, że to kółko kolorów generowane jest na `canvas`, przechwytuje zdarzenia myszki itd? Ja po prostu potrzebuję narzędzia do łatwego wybierania kolorów!). 66 | 67 | Oczywiście nie może być za dobrze. Problemy pojawiają się bardzo szybko, aż za szybko… Podstawowym jest sposób obsługi zdarzeń na elemencie, będącym shadow rootem. Można z góry zapomnieć o takich wynalazkach, jak event delegation: zdarzenie przypięte do elementu z ShadowDOM zarówno w `this`, jak i w `event.target` będzie wskazywać na ten element - granicy cienia nie da się przełamać. Bardzo szybko przekonałem się o tym i to dość boleśnie, próbując naskrobać swój 1. Web Component, jakim stała się [klawiatura ekranowa](http://dgui.comandeer.pl/components/dgui-keyboard/demo.html): odczytanie który dokładnie klawisz został wciśnięty nagle okazało się zajęciem dosyć karkołomnym. Cień udostępnia co prawda metody do pobierania jego dzieci i można bezpośrednio do nich przypinać zdarzenia, jednak ma to dwie wady: musimy pobrać wszystkie potrzebne nam dzieci i musimy do każdego przypiąć konkretne zdarzenie (łatwo policzyć, że dla kilku instancji takiej klawiatury na stronie będziemy mieli ponad 100 zdarzeń dla samych klawiszy!). Niezbyt wydajne, ale w ostateczności da się przeboleć. 68 | 69 | Polymer nie byłby jednak sobą, gdyby nie zaoferował szybkiego, wygodnego i całkowicie nieprzemyślanego rozwiązania. Mowa o atrybutach `[on-[event]]` dla każdego elementu z ShadowDOM, które musi mieć zdarzenie. Mam wrażenie, że [gdzieś to już widziałem](https://pornel.net/onclick). Wracamy do mieszania warstwy zachowania z… no właśnie - czym jest HTML w Web Components? Bo im bardziej wgłębiam się w ten temat, tym bardziej mam wrażenie, że HTML jest tutaj zawoalowanym odpowiednikiem JS. Niemniej te atrybuty jak dotąd są jedyną sensowną metodą dowiązywania zdarzeń do elementów w ShadowDOM (nie licząc iterowania po wszystkich możliwych węzłach shadow roota, co w Polymerze jest tak zgrabnie ukryte, że i tak lepiej dodać te atrybuty). Sądzę jednak, że `[on-click="{{clicker}}"]` wciąż wygląda lepiej niż `[ng-click="function()"]` - bo co do tego drugiego nie mamy żadnych wątpliwości, że całość przechodzi przez `eval`. 70 | 71 | Warto tu także wspomnieć o dostępnym w każdym elemencie `this.$`, przechowującym referencje do wszystkich elementów Shadow DOM, które mają nadane `[id]`. Miły akcent, jednak raczej bym oszalał dodając `[id]` do każdego elementu. 72 | 73 | 74 |

    HTML Imports

    75 | 76 | HTML Imports są HTML-ową wersją modułów CJS - są synchroniczne z natury. Problem polega na tym, że naturalnie występują tylko w Chrome i tylko w nim są synchroniczne. Wszystkie inne przeglądarki dostają zatem rozwiązanie asynchroniczne. Co to oznacza? Problemy. 77 | 78 | Postanowiłem w dGUI pewne wspólne helpery i inne tego typu dziwne rzeczy wydzielić do osobnego pliku JS i importować go jako właśnie HTML Import. Szczęśliwy, że działa w Chrome, scomittowałem zmiany. Oczywiście w lisku nie działało. Czemu? Z prostej przyczyny - wiedząc, że importy są synchroniczne, pozwoliłem sobie na założenie, że globalny obiekt `dGUI` (tak, globalny obiekt - jeśli ktoś mi pokaże sensowny przykład wykorzystania UMD/AMD z Web Components, bez setki niepotrzebnych udziwnień, wówczas chętnie to zmienię) istnieje. W lisku ewidentnie nie było to prawdą, bo całe importy fallbackują tam do żądań Ajaksem. Tym sposobem skrypt się wyglebił. 79 | 80 | Zatem jeśli chcemy się pobawić w dynamiczne zasysanie skryptów przy pomocy HTML Imports, a od tych skryptów zależeć ma cały nasz skomplikowany system, to lepiej zawczasu przygotować się na dziwne zabawy z asynchronicznością (zdarzenia, wdrożenie mimo wszystko AMD itp. dziwne praktyki, ocierające się o voodoo). 81 | 82 | 83 |

    Dostępność

    84 | 85 | Web Components to nie HTML. Tutaj nie ma żadnych wartości semantycznych - wszystko należy budować od podstaw. Co to oznacza? ARIA, naprawdę sporo ARIA. I to do oznaczenia rzeczy najbardziej podstawowych, od zera. Taka jest cena za innowację. 86 | 87 | 88 |

    I co dalej?

    89 | 90 | Web Components, jako zbiór naprawdę nowych technologii, cierpi na bardzo poważne problemy wieku dziecięcego. Mimo wszystko uważam jednak, że - gdy w końcu sytuacja się ustabilizuje - stanie się sensownym sposobem na implementację zenkapsulowanego, wydajnego i prostego w użyciu interfejsu użytkownika. Mam także nadzieję, że inni webmasterzy również dojdą do tego samego wniosku i przestaną przepisywać na Web Components wszystko, łącznie z rzeczami, które nigdy nie powinny być deklaratywne. Na razie jednak Web Components pozostają ciekawostką - potężną, lecz tylko ciekawostką. Jedynym sensownym jej użyciem prawdopodobnie są ściśle kontrolowane środowiska (typu [node-webkit](https://github.com/rogerwang/node-webkit)). I na tym jak na razie zakres stosowania Web Components się kończy. 91 | 92 | Sama technologia nie jest zła - większość tutaj opisanych zarzutów de facto tyczy się Polymera a nie Web Components per se. Niemniej - nie ma obecnie jakiegokolwiek sensu pisać w Web Components nie używając Polymera. I koło się zamyka. W przyszłości pewnie powstaną lekkie i o wiele przyjaźniejsze wrappery na Web Components niż Polymer. No właśnie - wrappery. A do tego jeszcze bardzo długa droga, po wyboistych grzbietach polyfillów… 93 | -------------------------------------------------------------------------------- /build/tutorials/svg-sprites.md: -------------------------------------------------------------------------------- 1 | Czyli jak zrobić odpowiednik Font Awesome przy pomocy SVG. 2 | 3 |

    [CSS] Sprite'y w SVG

    4 | 5 |
    6 | 7 | Ten tutorial powstał bardzo dawno temu i od tego czasu zmieniło się sporo! Polecam [artykuł na CSS-Tricks.com](http://css-tricks.com/svg-fragment-identifiers-work/), opisujący temat o wiele dokładniej. 8 | 9 |
    10 | 11 | Czasami sprite'y w CSS mogą być wkurzające 12 | 13 | ```css 14 | .help 15 | { 16 | background: url(super-hiper-sprite.png) no-repeat -567px -234px; 17 | } 18 | ``` 19 | 20 | Jednak ostatnio [ktoś](http://xn--dahlstrm-t4a.net/) wpadł na genialny pomysł: zróbmy to w SVG! 21 | 22 | Na czym polega cały trick? Otóż chodzi o zapisanie w jednym pliku SVG kilku ikonek. Do tego dodano trochę magii z CSS3, aby tylko potrzebna nam ikonka się pokazywała. Ta magia jest już dobrze znana: 23 | 24 | ```css 25 | .icon {display:none;} 26 | .icon:target {display:block;} 27 | ``` 28 | 29 | Stwórzmy sobie zatem prosty pliczek SVG: 30 | 31 | ```markup 32 | 33 | 34 | 35 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ``` 56 | 57 | najwyższych lotów to on nie jest, wiem, ale do prezentacji starczy ;) 58 | 59 | Ot, nic nadzwyczajnego - mamy dwie ikonki: jakieś [dwa prostokąciki](http://comandeer.pl/tutorials/res/svg-sprites/stack.svg#rect) i [dwie linie](http://comandeer.pl/tutorials/res/svg-sprites/stack.svg#line). Jak widać, początkowo obydwie są ukryte, ale `:target` działa :D Ważna dla nas jest klasa `.icon` i `id` poszczególnych ikonek (do nich się będziemy odwoływać). Można oczywiście jeszcze bardziej uprościć kod i pozbyć się klasy `.icon`, posługując się selektorem `g`. 60 | 61 | W HTML i CSS też wielkiego problemu nie ma: 62 | 63 | ```markup 64 | 72 | 73 | ``` 74 | 75 | Ot, zwykłe tło w formacie SVG... I tu pojawia się problem, bo technika ta działa tylko w Firefoxie, co mnie osobiście dziwi (ej, w końcu SVG to wieloletni standard de facto!). Na szczęście wkrótce wsparcie ma się pojawić także w Operze i reszcie. 76 | 77 | Technika IMO bardzo pomocna i obiecująca. Nie dość, że łatwiej się nią posługiwać (nie trzeba wyliczać pozycji tła), to jeszcze obrazki automatycznie się skalują do wielkości elementu (można to sprawdzić, zmieniając wielkość naszego przycisku). No i teoretycznie powinno to ważyć mniej niż zwykły obrazek ;) 78 | 79 | 80 | [[size=14]DEMO[/size]](http://tutorials.comandeer.pl/res/svg-sprites) 81 | 82 | 83 |
    84 | 85 | Artykuł można potraktować jako bardzo bardzo luźne tłumaczenie [notki Simuraia](http://simurai.com/post/20251013889/svg-stacks). W porównaniu do orginalnej metody, lekko uprościłem kod SVG. 86 | 87 |
    88 | -------------------------------------------------------------------------------- /build/tutslist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'HTML': { 3 | 'Semantyczny blog w HTML5': 'html5-blog' 4 | }, 5 | 6 | 'JavaScript': { 7 | 'Wypieść swój JS': 'js-beauty', 8 | 'Dynamiczne wczytywanie skryptów': 'js-dynamic', 9 | 'Ukrywanie ciała funkcji': 'js-hiding', 10 | 'Czemu nie potrzebujesz jQuery?': 'js-jquery', 11 | 'Data po polsku': 'js-intl', 12 | 'WebComponents i Polymer - niespełniony sen?': 'polymer' 13 | }, 14 | 'CSS': { 15 | 'Sprite\'y w SVG': 'svg-sprites' 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SiteBuilder", 3 | "version": "0.0.0", 4 | "main": "Gruntfile.js", 5 | "private": true, 6 | "dependencies": { 7 | "cheerio": "^0.22.0", 8 | "markdown-it": "^12.0.2", 9 | "markdown-it-link-attributes": "^3.0.0", 10 | "puppeteer": "^5.4.1" 11 | }, 12 | "author": "Comandeer", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "cz-conventional-changelog": "^3.3.0", 16 | "http-server": "^0.12.3", 17 | "node-sass": "^5.0.0", 18 | "rollup": "^2.33.1", 19 | "rollup-plugin-babel-minify": "^10.0.0" 20 | }, 21 | "scripts": { 22 | "start": "http-server", 23 | "build": "node ./build/bs/build && rollup -c build/bs/rollup.config.js && node-sass build/bs/scss/main.scss public/css/main.css --output-style compressed", 24 | "generate-pdf": "node build/generate-pdf.js", 25 | "dist": "npm run build && npm run generate-pdf" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------- 2 | # Expires headers (for better cache control) 3 | # ---------------------------------------------------------------------- 4 | 5 | # These are pretty far-future expires headers. 6 | # They assume you control versioning with filename-based cache busting 7 | # Additionally, consider that outdated proxies may miscache 8 | # www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ 9 | 10 | # If you don't use filenames to version, lower the CSS and JS to something like 11 | # "access plus 1 week". 12 | 13 | 14 | ExpiresActive on 15 | 16 | # Perhaps better to whitelist expires rules? Perhaps. 17 | ExpiresDefault "access plus 1 month" 18 | 19 | # cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5) 20 | ExpiresByType text/cache-manifest "access plus 0 seconds" 21 | 22 | # Your document html 23 | ExpiresByType text/html "access plus 0 seconds" 24 | 25 | # Data 26 | ExpiresByType text/xml "access plus 0 seconds" 27 | ExpiresByType application/xml "access plus 0 seconds" 28 | ExpiresByType application/json "access plus 0 seconds" 29 | 30 | # Feed 31 | ExpiresByType application/rss+xml "access plus 1 hour" 32 | ExpiresByType application/atom+xml "access plus 1 hour" 33 | 34 | # Favicon (cannot be renamed) 35 | ExpiresByType image/x-icon "access plus 1 week" 36 | 37 | # Media: images, video, audio 38 | ExpiresByType image/gif "access plus 1 month" 39 | ExpiresByType image/png "access plus 1 month" 40 | ExpiresByType image/jpeg "access plus 1 month" 41 | ExpiresByType video/ogg "access plus 1 month" 42 | ExpiresByType audio/ogg "access plus 1 month" 43 | ExpiresByType video/mp4 "access plus 1 month" 44 | ExpiresByType video/webm "access plus 1 month" 45 | 46 | # HTC files (css3pie) 47 | ExpiresByType text/x-component "access plus 1 month" 48 | 49 | # Webfonts 50 | ExpiresByType application/x-font-ttf "access plus 1 year" 51 | ExpiresByType font/opentype "access plus 1 year" 52 | ExpiresByType application/x-font-woff "access plus 1 year" 53 | ExpiresByType image/svg+xml "access plus 1 year" 54 | ExpiresByType application/vnd.ms-fontobject "access plus 1 year" 55 | ExpiresByType application/font-woff "access plus 1 year" 56 | ExpiresByType application/x-font-woff2 "access plus 1 year" 57 | ExpiresByType application/font-woff2 "access plus 1 year" 58 | 59 | # CSS and JavaScript 60 | ExpiresByType text/css "access plus 1 year" 61 | ExpiresByType application/javascript "access plus 1 year" 62 | 63 | 64 | 65 | 66 | 67 | AddOutputFilter DEFLATE js css html 68 | 69 | -------------------------------------------------------------------------------- /public/css/prism.css: -------------------------------------------------------------------------------- 1 | code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{pointer-events:none;display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar>.toolbar .toolbar-item{display:inline-block}div.code-toolbar>.toolbar a{cursor:pointer}div.code-toolbar>.toolbar button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.code-toolbar>.toolbar a,div.code-toolbar>.toolbar button,div.code-toolbar>.toolbar span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar a:focus,div.code-toolbar>.toolbar a:hover,div.code-toolbar>.toolbar button:focus,div.code-toolbar>.toolbar button:hover,div.code-toolbar>.toolbar span:focus,div.code-toolbar>.toolbar span:hover{color:inherit;text-decoration:none} 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /public/google46fa397f2bc00dac.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google46fa397f2bc00dac.html -------------------------------------------------------------------------------- /public/html5-blog.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/html5-blog.pdf -------------------------------------------------------------------------------- /public/images/comandeer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/images/comandeer.jpg -------------------------------------------------------------------------------- /public/images/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/images/line.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lista tutorialów 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    40 | 41 |
    42 |
    43 |

    Tutoriale Comandeera

    44 |

    Na tej stronie znajdziesz listę moich tutorialów i artykułów, które piszę od czasu do czasu.

    45 | 46 |

    Lista tutorialów

    47 |
    48 |
    HTML
    49 | Semantyczny blog w HTML5 50 |
    JavaScript
    51 | Wypieść swój JS 52 |
    53 | Dynamiczne wczytywanie skryptów 54 |
    55 | Ukrywanie ciała funkcji 56 |
    57 | Czemu nie potrzebujesz jQuery? 58 |
    59 | Data po polsku 60 |
    61 | WebComponents i Polymer - niespełniony sen? 62 |
    CSS
    63 | Sprite'y w SVG 64 |
    65 |
    66 | 67 |

    Pozostałe artykuły

    68 |

    Tak, czasami pisuję gdzie indziej.

    69 | 88 |

    Dodatkowo jestem także redaktorem WebKrytyka, a wolnych chwilach prowadzę osobistego bloga.

    89 |
    90 |
    91 | 92 |
    93 |
    94 |

    Copyright © by .

    95 |
    96 |
    97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /public/js-beauty.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/js-beauty.pdf -------------------------------------------------------------------------------- /public/js-dynamic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/js-dynamic.pdf -------------------------------------------------------------------------------- /public/js-hiding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | [JavaScript] Ukrywanie ciała funkcji 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 45 | 46 |
    47 |
    48 |
    49 |

    [JavaScript] Ukrywanie ciała funkcji

    50 | 51 | Wersja PDF 52 |
    53 |
    54 |
    55 | 63 | 64 |
    65 |

    66 | 67 |
    68 |

    Niestety, w nowych wersjach Chrome ta metoda już nie działa i dostajemy pełen dostęp do ciała funkcji.

    69 |
    70 |
    71 |

    Na wstępie uprzedzę, że technika ta nie powstała w mym płodnym umyśle. Została znaleziona przez developera Opery w czasie debugowania pewnego upierdliwego błędu w działaniu Google Maps w tejże przeglądarce. Jak nietrudno zgadnąć, zostało to w bólach znalezione w źródle tego zaawansowanego webappa. Ja jedynie postanowiłem to wykorzystać na szerszą skalę. Trzeba też zaznaczyć, że technika ta jest średnio przydatna jeśli nie zaciemnimy równocześnie źródła skryptu (np. poprzez uglify.js czy też GCC).

    72 |
    73 |

    O co wgl chodzi? A no chodzi o pewną magiczną właściwość poniższego fragmentu kodu:

    74 |
    Function.prototype
     75 | 
    76 |

    Niby co w tym takiego nadzwyczajnego? Już mówię!

    77 |
    console.log( Function.prototype.call.apply( Function.prototype.bind, [ function(){} ] ) );
     78 | 
    79 |

    Zapewne w większości powyższy kod wzbudza podświadomy lęk. Przyznać trzeba, że naprawdę wygląda dość dziwacznie. Ale postaram się troszkę przybliżyć jego sens.

    80 |

    Zacznijmy od Function.prototype. Jak już kiedyś wspomniałem, JS jest takim mocno zboczonym językiem, gdzie wszystko jest obiektem. Nic zatem dziwnego, że funkcje są tak naprawdę… obiektami "klasy" Function ("klasy", bo jak wiadomo ECMAScript to jedyna grupa języków prototypowanych). Zatem każda zmiana w prototypie tego globalnego obiektu będzie dziedziczona przez wszystkie funkcje. Jednak tutaj skrypterzy Google poszli o krok dalej i wywołują poszczególne metody prosto z prototypu (czasami, ale to bardzo rzadko, okazuje się to przydatne)!

    81 |

    Zatem jakie metody się tu wywołuje? call, apply i bind. Wszystkie działają dość podobnie i ich głównym zadaniem jest zmienienie kontekstu wywołania funkcji (this). Już wyjaśniam na przykładzie:

    82 |
    var d = { n: 1 };
     83 | 
     84 | function g() {
     85 | 	console.log( this );
     86 | }
     87 | 
     88 | g.apply( d );
     89 | 
    90 |

    To wywołanie funkcji wyrzuci do konsoli obiekt d zamiast standardowego window.Dość użyteczne, pod warunkiem, że pisze się obiektowy JS i trochę kojarzy pojęcie kontekstu wywołania. Dla nas jednak nie jest to aż tak ważne. Wystarczy wiedzieć, że apply przyjmuje jako pierwszy parametr obiekt, który ma zastąpić nam this, a drugim parametrem jest tablica parametrów wywoływanej funkcji. Przykład, bo brzmi to dziwnie:

    91 |
    function g( r ) {
     92 | 	console.log( r );
     93 | };
     94 | 
     95 | g.apply( window, [ 'whatever' ] );
     96 | 
    97 |

    W konsoli ujrzymy wspaniały napis whatever. call w zasadzie działa tak samo, ale zamiast tablicy parametrów, przyjmuje ich listę:

    98 |
    function g( r ) {
     99 | 	console.log( r );
    100 | };
    101 | 
    102 | g.call( window, 'whatever' );
    103 | 
    104 |

    Efekt identyczny jak w poprzednim przykładzie. Trochę inaczej działa bind, ponieważ nie wywołuje funkcji w podanym kontekście, a zwraca jej "klona", działającego w danym scope. Dla kompletności przykład, z wiadomym wynikiem:

    105 |
    function g( r ) {
    106 | 	console.log( r );
    107 | };
    108 | ( g.bind( window, [ 'whatever' ] ) )();
    109 | 
    110 |

    Zatem gdy trochę się wysili szare komórki, można dojść do pewnego uogólnienia, że w bardzo zawoalowany sposób po prostu wywołujemy Function.prototype.bind.

    111 |

    OK, a teraz gwóźdź programu: co nam daje połączenie tych trzech metod? A no, przy dobrze zaciemnionym kodzie bardzo utrudnia podejrzenie działania naszych funkcji i ukrywa to przed debuggerami. Kod

    112 |
    console.log( Function.prototype.call.apply( Function.prototype.bind, [ function() {} ] ) );
    113 | 
    114 |

    zwróci nam bowiem nic nie mówiące

    115 |
    function() { [native code] }
    116 | 
    117 |

    Chyba nie muszę mówić jaką konsternację na twarzy młodocianego hakiera zrobi informacja, że nasza super-hiper-tajna funkcja, w której zaimplementowaliśmy czekający na opatentowanie algorytm, uparcie twierdzi, że jest funkcją wbudowaną i to na dodatek bez nazwy!

    118 |

    I zanim polecisz opakowywać wszystkie swoje funkcje w ten sposób, wiadro zimnej wody dla ochłody: KOMPATYBILNOŚĆ. Tak, wiem, że wiesz, że ja wiem, że wiesz, że nie działa to w IE < 9 – przecież to logiczne! Ale niestety rynek tych przeglądarek wciąż jest dośc spory. Toteż – dla własnej wygody i kompatybilności – napiszmy sobie prostą funkcję pomocniczą. Nazwijmy ją (a jakże!) obfuscate. Użyjemy tu dwóch bardziej zaawansowanych elementów JS: funkcji natychmiastowego wywołania oraz closures (i w tym miejscu większość mniej zdeterminowanych postanowiła zamknąć tą kartę przeglądarki). Niech Was nie zwiodą pozory – wcale to (aż tak) trudne nie będzie! Na początku zadeklarujmy sobie swoją funkcję:

    119 |
    var obfuscate = ( function() {
    120 | }() );
    121 | 
    122 |

    To jest właśnie tzw. funkcja natychmiastowego wywołania (IIFE – Immediately Invoked Function Expression). Jak nietrudno zgadnąć, nazywa się tak, bo od razu po zadeklarowaniu jest wywoływana (to tak naprawdę jest wywołanie funkcji anonimowej). Na razie nasz kod jest bezużyteczny, bo zmienna obfuscate będzie miała wartość undefined. Toteż trzeba dodać do naszej funkcji return:

    123 |
    var obfuscate = ( function() {
    124 | 	return function( func ) {
    125 | 		return Function.prototype.call.apply( Function.prototype.bind, [ func ] );
    126 | 	};
    127 | }() )
    128 | 
    129 |

    Tak, to jest właśnie closure – funkcja zwracana przez inną funkcję, zamknięta w jej obszarze (no ej, w końcu to technika od samego Google; nikt nie mówił, że łatwo będzie). Tak naprawdę, gdy JS się wczyta, przeglądarka otrzyma coś takiego:

    130 |
    var obfuscate = function( func ) {
    131 | 	return Function.prototype.call.apply( Function.prototype.bind, [ func ] );
    132 | }
    133 | 
    134 |

    Czemu zatem nie zapisałem tego w taki sposób? Otóż przez IE 8 tak nie zapisałem! Trzeba przecież sprawdzić czy ta metoda ukrywania kodu jest wspierana i w zależności od tego funkcja obfuscate przyjąć może dwie postaci:

    135 |
      136 |
    • zaciemni nam kod,
    • 137 |
    • zwróci niezaciemniony kod.
    • 138 |
    139 |

    A jak sprawdzić czy technika jest obsługiwana? Można sprawdzić czy przeglądarka wysypie się na wywołaniu zaciemnionej w taki sposób funkcji. Ok, ale to nam zatrzyma wykonywanie skryptu… chyba że wrzucimy do tego obsługę wyjątków!

    140 |
    try {
    141 | 	Function.prototype.call.apply( Function.prototype.bind, [ function() {} ] )();
    142 | } catch ( err ) {}
    143 | 
    144 |

    Dobra, zwracanie niezaciemnionego kodu jest łatwe – wystarczy zwrócić parametr func. Tyle. Zatem po połączeniu wszystkiego razem, otrzymamy coś takiego:

    145 |
    var obfuscate = ( function() {
    146 | 	var a = Function.prototype;
    147 | 
    148 | 	try {
    149 | 		a.call.apply( a.bind, [ function() {} ] )();
    150 | 	} catch ( err ) {
    151 | 		return function( func ) {
    152 | 			return func;
    153 | 		};
    154 | 	}
    155 | 
    156 | 	return function( func ) {
    157 | 		return a.call.apply( a.bind, [ func ] );
    158 | 	};
    159 | }() );
    160 | 
    161 |
      162 |
    • Najpierw przypisujemy sobie Function.prototype do zmiennej a (żeby się później nie napisać i oszczędzić te parę bajtów).
    • 163 |
    • Sprawdzamy, czy obfuskacja w ogóle działa w bloku try/catch. Jeśli nie, jak gdyby nigdy nic zwracamy funkcję zwracającą parametr func (nieźle to brzmi).
    • 164 |
    • W innych przypadkach po prostu zwracamy funkcję zwracającą zaciemnioną funkcję
    • 165 |
    166 |

    Przykład:

    167 |
    var f = obfuscate( function( n, r ) {
    168 | 	return n + ':' + r;
    169 | } );
    170 | console.log( f( 'r', 't' ) ); // "r:t"
    171 | console.log( f ); // function () { [native code] }
    172 | 
    173 |

    Demo online nie wstawiam, bo raczej za dynamiczne by nie było.

    174 |

    Aaaaa, i taka uwaga na koniec: moja funkcja obfuscate bardzo lubi strict mode!

    175 | 176 |
    177 |
    178 |
    179 | 180 |
    181 |
    182 |

    Copyright © by .

    183 |
    184 |
    185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /public/js-hiding.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/js-hiding.pdf -------------------------------------------------------------------------------- /public/js-intl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | Data po polsku 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 45 | 46 |
    47 |
    48 |
    49 |

    Data po polsku

    50 | 51 | Wersja PDF 52 |
    53 |
    54 |
    55 | 65 | 66 |
    67 |

    68 | 69 |

    Dzisiaj będzie krótko, rzeczowo i na temat (co dość rzadko mi się zdarza). Otóż: jak dobrze zrobić datę po polsku w JS i się przy tym nie narobić.

    70 |

    Po staremu

    71 |

    Dotąd jedynym właściwym sposobem były różne dziwne kombinacje typu "stwórzmy sobie tablicę z polskimi nazwami miesięcy i dni tygodnia, a następnie podstawiajmy pod dane, zwracane przez new Date". Mogą one wyglądać rozwlekle i odpychająco (a ich autorem nie jest Polak). Mogą być też wzorowane na innych językach, stając się jeszcze bardziej rozwlekłe i… nie JS-owe. Mogą w końcu zmienić się po prostu w wywołanie odpowiedniej, krótkiej funkcyjki w PHP, po stwierdzeniu, że JS się do tego nie nadaje.

    72 |

    73 |

    Intl na ratunek!

    74 |

    75 |

    Na szczęście się to zmieniło. I to nie znowu tak niedawno (bo w 2012). Wtedy powstał standard ECMA-402 (ECMAScript Internationalization API). Oczywiście na odzew ze strony producentów przeglądarek trzeba było ciut poczekać, niemniej – jeśli wierzyć MDN – działa dziś wszędzie, oprócz Safari i – co prawdopodobnie może być o wiele ważniejsze – node.js (jednak od czego jest niezawodny GitHub i polyfille; oczywiście w node.js/io.js obsługę można sobie wkompilować).

    76 |

    Cały standard składa się z 3 głównych "klas", zamkniętych w krótkiej i schludnej przestrzeni nazw – Intl. Są to:

    77 |
      78 |
    • Intl.Collator – służący do porównywania tekstu
    • 79 |
    • Intl.NumberFormat – służący do formatowania wszelkiej maści liczb (między innymi walut czy liczebników porządkowych)
    • 80 |
    • Intl.DateTimeFormat – tak, tego właśnie użyjemy
    • 81 |
    82 |

    Formatujemy datę

    83 |

    Żeby wyświetlić ładną datę po polsku, musimy stworzyć nowy obiekt "klasy" Intl.DateTimeFormat. Jako pierwszy parametr konstruktor bierze nazwę języka (oczywiście zgodną ze standardem ISO, zatem dwuliterowy skrót):

    84 |
    var formatter = new Intl.DateTimeFormat( 'pl' );
     85 | 
    86 |

    Już! Pierwszy krok za nami. Mamy obiekt wyświetlający datę po polsku. Udostępnia on aż jedną metodę – format – która formatuje przekazaną jej datę (jeśli jej nie podamy, zastosuje aktualną – przynajmniej w Chrome; polecam jednak ją podawać dla zwiększenia czytelności kodu).

    87 |
    formatter.format( new Date() ); // 23.7.2016
     88 | 
    89 |

    Jak widać, mało imponujące. Równie dobrze można to uznać za datę po angielsku. Na pomoc przychodzi nam drugi parametr konstruktora Intl.DateTimeFormat – jest to obiekt opcji. Przyjrzyjmy się takiemu przykładowi:

    90 |
    var formatter = new Intl.DateTimeFormat( 'pl', {
     91 | 	day: 'numeric',
     92 | 	month: 'long',
     93 | 	year: 'numeric'
     94 | } );
     95 | formatter.format( new Date() ); // 23 lipca 2016
     96 | 
    97 |

    Voila! To jest dokładnie to, o co nam chodziło. Oczywiście opcji jest więcej, po dokładny ich spis zapraszam na MDN.

    98 | 99 |
    100 |
    101 |
    102 | 103 |
    104 |
    105 |

    Copyright © by .

    106 |
    107 |
    108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /public/js-intl.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/js-intl.pdf -------------------------------------------------------------------------------- /public/js-jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | Czemu nie potrzebujesz jQuery? 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 45 | 46 |
    47 |
    48 |
    49 |

    Czemu nie potrzebujesz jQuery?

    50 | 51 | Wersja PDF 52 |
    53 |
    54 |
    55 | 67 | 68 |
    69 |

    70 | 71 |

    Mimo że zdaje ci się inaczej

    72 |

    Ten tutorial kieruję do wszystkich tych, którzy nie wyobrażają sobie wykonania jakiejkolwiek prostej czynności w JS bez użycia tej wszechstronnej biblioteki. Część zarzutów tyczy się też mnie, gdyż osobiście również korzystam z jQuery (hej, to nie moja wina, że DOM jest tak spaprany!)

    73 |

    Alternatywa?

    74 |

    VanillaJS + jeden z tych maluczkich

    75 |

    Podstawowe operacje DOM-owe

    76 |

    Wyszukiwanie po selektorach

    77 |

    Potęgą jQuery jest jego silniczek Sizzle, który pozwala wyszukać dowolny element tylko i wyłącznie na podstawie selektora. To jest często główny/jedyny powód, dla którego część webmasterów sięga po jQ.

    78 |
    $( '#bardzo > optymalny .selektor #ktory .na-bank.dziala' );
     79 | 
    80 |
    81 |

    Oczywiście optymalizacja takich selektorów to zupełnie inna sprawa.

    82 |
    83 |

    Skupmy się na tym, czy w czystym JS istnieje jakaś alternatywa dla tego pięknego owijacza? Otóż tak!

    84 |
    document.querySelectorAll( '#bardzo > optymalny .selektor #ktory .na-bank.dziala' );
     85 | 
    86 |

    Prawdę mówiąc, jeśli przyjrzymy się kodowi jQ, zauważymy, że wewnętrznie używa on właśnie tej metody. Nie trzeba chyba mówić, co jest szybsze. Oczywiście można pójść o krok dalej i zrobić sobie coś takiego:

    87 |
    var $ = document.querySelectorAll.bind( document );
     88 | 
    89 |

    Voila! Mamy jQ bez jQ.

    90 |

    Chainowanie metod

    91 |

    Chainowanie metod to to, co decyduje o niezwykłej użyteczności jQ w zastosowaniach DOM-owych.

    92 |
    $( '<a>' ).attr( 'href', '#' ).on( 'click', function() {} );
     93 | 
    94 |

    Owszem, w VanillaJS nie jestem w stanie uzyskać tak wygodnej obsługi DOM. Ale tu z pomocą przychodzi nam Chainvas – małe cudeńko, które jest w stanie złańcuchować de facto wszystko. Dzięki temu można spokojnie napisać:

    95 |
    document.createElement( 'a' ).setAttribute( 'href', '#' ).addEventListener( 'click', function() {} );
     96 | 
    97 |

    I tym sposobem odebraliśmy jQ największą jego przewagę w operacjach DOM-owych, oszczędzając przy tym 31KB kodu (oczywiście mowa tu o wersjach gzipniętych i zminifikowanych).

    98 |

    Event delegation

    99 |

    w jQ jest prosto:

    100 |
    $( document ).on( 'click', '.clicky', function( evt ) {
    101 | 	//awesome stuff
    102 | } );
    103 | 
    104 |

    w czystym JS też jest prosto

    105 |
    document.addEventListener( 'click', function( evt ) {
    106 | 	if ( !evt.target || !e.target.matches( '.clicky' ) /* Można zmienić na !evt.target.classList.contains( 'clicky' )*/ ) {
    107 | 		return;
    108 | 	}
    109 | 	//awesome stuff
    110 | }, false );
    111 | 
    112 |

    113 |

    .clone

    114 |

    115 |

    Żeby sklonować obiekt w jQ, wystarczy skorzystać z metody .clone. Czy da się zrobić coś takiego w czystym JS? A i owszem.

    116 |
    function clone( obj ) {
    117 | 	return obj.cloneNode ? obj.cloneNode( obj ) : JSON.parse( JSON.stringify( obj ) );
    118 | }
    119 | console.log( clone( document.createElement( 'a' ) ) );
    120 | 
    121 |

    Prosta i przyjemna sztuczka.

    122 |

    123 |

    .extend

    124 |

    125 |

    To już nie jest do końca DOM, ale może się tu nadać.

    126 |

    Jak wiadomo, w jQ mamy metodę .extend rozszerzającą nam obiekt:

    127 |
    console.log( $.extend( {}, { cos: 1 } ) );
    128 | 
    129 |

    W czystym JS też się da. Ba, można być nawet chamskim i po prostu wyciągnąć ten kod z jQ.

    130 |

    Animacje

    131 |

    Co tu dużo mówić – od kiedy w CSS są transition i animation, większość rzeczy można w ogóle wyjąć poza JS. Natomiast niektóre inne animacje z jQ można równie łatwo napisać w czystym JS. Przykład, lekko poprawiony, ze strony VanillaJS:

    132 |
    $( '#thing' ).fadeOut();
    133 | //vs
    134 | var s = document.getElementById( 'd' ).style;
    135 | s.opacity = +( s.opacity || window.getComputedStyle( el )[ 'opacity' ] );
    136 | ( function f() { ( s.opacity -= 1 / 100 ) <= 0 ? s.display = "none" : requestAnimationFrame( f ) }() );
    137 | 
    138 |

    Nie trzeba chyba zaznaczać, że porównanie objętościowe tych trzech linijek i całego jQ wypada korzystnie dla czystego JS.

    139 |

    oczywiście warto zauważyć, że rAF jest tylko w nowszych browserach i trza by zarzucić jakimś fallbackiem:

    140 |
    window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function( f ){ setTimeout( f, 1000 / 60 ); };
    141 | 
    142 |

    XHR

    143 |

    Tu nie będzie przykładów, jedynie krótkie info.

    144 |

    Otóż wrapper w jQ jest cholernie wygodny i w ogóle. Jednak jeśli chcemy mieć dostęp do bardziej zaawansowanych własności obiektu xhr (np. xhr.upload.progress), trzeba użyć natywnego obiektu.

    145 |

    jQUI

    146 |

    Wiele rzeczy da się napisać bez tego, wystarcza trochę pomyślunku.

    147 |

    Szybki przykład na resize elementów (akurat wykorzystywany w zupełnie innym kontekście niż browserowy):

    148 |
    if ( [ 'l', 'r' ].indexOf( resizing ) !== -1 ) {
    149 | 	if ( resizing === 'l' ) {
    150 | 		window.frame.width += x - e.pageX;
    151 | 		window.frame.left -= x - e.pageX;
    152 | 	} else {
    153 | 		window.frame.width -= x - e.pageX;
    154 | 	}
    155 | 
    156 | 	if ( window.frame.width < 60 ) {
    157 | 		window.frame.width = 60;
    158 | 	}
    159 | 
    160 | 	x = e.pageX;
    161 | 	y = e.pageY;
    162 | } else if ( [ 't', 'b' ].indexOf( resizing ) !== -1 ) {
    163 | 	if ( resizing === 't' ) {
    164 | 		window.frame.height += y - e.pageY;
    165 | 		window.frame.top -= y - e.pageY;
    166 | 	} else {
    167 | 		window.frame.height -= y - e.pageY;
    168 | 	}
    169 | 
    170 | 	if ( window.frame.height < 120 ) {
    171 | 		window.frame.height = 120;
    172 | 	}
    173 | 
    174 | 	x = e.pageX;
    175 | 	y = e.pageY;
    176 | } else {
    177 | 	window.frame.width -= x-e.pageX;
    178 | 	window.frame.height -= y-e.pageY;
    179 | }
    180 | 
    181 |

    co prawda kod najwyższych lotów nie jest i raczej przedstawia ogólną ideę jak to może wyglądać, niż jak to powinno wyglądać. Ba, można zdać się na natywne funkcje, takie jak resize w CSS (miało działać na wszystkim, ale nie wiem czy ta obietnica jest spełniona) i drag&drop (w IE od wersji 5 bodaj!). Dobitnie to pokazuje, że nawet tak skomplikowane czynności jakie wykonuje za nas jQUI można zrobić zarówno bez niego, jak i jego ojca – jQ.

    182 |

    Ale za to w jQ da się…

    183 |

    Z racji tego, że jQ jest tylko subsetem JS, śmiem twierdzić, że nie ma rzeczy, której nie dałoby się osiągnąć w czystym JS. Jestem w stanie podjąć każde wyzwanie i udowodnić, że się da bez!

    184 |

    Czy porzucić?

    185 |

    Nie trzeba. Sam używam i jestem zadowolony. Cały problem polega na tym, że jQuery jest przedstawiane jako lekarstwo na wszelkie zło JS, a to po prostu wierutna bzdura. jQuery powstało jako helper DOM-owy i to wciąż jest jego główną siłą.

    186 |

    Co więcej, jQuery niszczy świadomość programistów sieciowych. Coraz częściej można spotkać bowiem nie programistów JS, a programistów jQuery, którzy jedynie tą biblioteką są w stanie się posługiwać.

    187 |

    Dlatego nie mówię, żeby przestać jQ używać. Jeśli natomiast do stronki, gdzie trza oskryptować jeden link, wsadzasz jQ, to znak, że coś jest nie tego. Jeśli natomiast umiesz zastąpić jQ własnym rozwiązaniem i robisz to, gdy potrzebujesz jedną, konkretną rzecz, to oznacza, że jesteś zupełnie normalnym użytkownikiem tej biblioteki i syndrom uzależnienia Ciebie nie dotyczy.

    188 | 189 |
    190 |
    191 |
    192 | 193 |
    194 |
    195 |

    Copyright © by .

    196 |
    197 |
    198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /public/js-jquery.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/js-jquery.pdf -------------------------------------------------------------------------------- /public/js/cookies.js: -------------------------------------------------------------------------------- 1 | /*! (C) Cookie Banner v1.2.2 - MIT License - http://cookiebanner.eu/ */ 2 | !function(e){"use strict";function t(e,t){var i=!1,o=!0,n=e.document,s=n.documentElement,r=n.addEventListener?"addEventListener":"attachEvent",a=n.addEventListener?"removeEventListener":"detachEvent",c=n.addEventListener?"":"on",l=function(o){"readystatechange"==o.type&&"complete"!=n.readyState||(("load"==o.type?e:n)[a](c+o.type,l,!1),!i&&(i=!0)&&t.call(e,o.type||o))},p=function(){try{s.doScroll("left")}catch(e){return void setTimeout(p,50)}l("poll")};if("complete"==n.readyState)t.call(e,"lazy");else{if(n.createEventObject&&s.doScroll){try{o=!e.frameElement}catch(e){}o&&p()}n[r](c+"DOMContentLoaded",l,!1),n[r](c+"readystatechange",l,!1),e[r](c+"load",l,!1)}}var i=e,o=i.document,n="cbinstance",s={get:function(e){return decodeURIComponent(o.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*"+encodeURIComponent(e).replace(/[-.+*]/g,"\\$&")+"\\s*\\=\\s*([^;]*).*$)|^.*$"),"$1"))||null},set:function(e,t,i,n,s,r){if(!e||/^(?:expires|max-age|path|domain|secure)$/i.test(e))return!1;var a="";if(i)switch(i.constructor){case Number:a=i===1/0?"; expires=Fri, 31 Dec 9999 23:59:59 GMT":"; max-age="+i;break;case String:a="; expires="+i;break;case Date:a="; expires="+i.toUTCString()}return o.cookie=encodeURIComponent(e)+"="+encodeURIComponent(t)+a+(s?"; domain="+s:"")+(n?"; path="+n:"")+(r?"; secure":""),!0},has:function(e){return new RegExp("(?:^|;\\s*)"+encodeURIComponent(e).replace(/[-.+*]/g,"\\$&")+"\\s*\\=").test(o.cookie)},remove:function(e,t,i){return!(!e||!this.has(e))&&(o.cookie=encodeURIComponent(e)+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT"+(i?"; domain="+i:"")+(t?"; path="+t:""),!0)}},r={merge:function(){var e,t={},i=0,o=arguments.length;if(0===o)return t;for(;i',s=o.createElement("div");s.innerHTML=n,e=s.firstChild}return e},agree:function(){return this.cookiejar.set(this.options.cookie,1,this.options.expires,this.options.cookiePath,""!==this.options.cookieDomain?this.options.cookieDomain:"",!!this.options.cookieSecure),!0},agreed:function(){return this.cookiejar.has(this.options.cookie)},close:function(){return this.inserted&&(this.closed||(this.element&&this.element.parentNode.removeChild(this.element),this.element_mask&&this.element_mask.parentNode.removeChild(this.element_mask),this.closed=!0)),this.closed},agree_and_close:function(){return this.options.debug||this.agree(),this.close()},cleanup:function(){return this.close(),this.unload()},unload:function(){return this.script_el&&this.script_el.parentNode.removeChild(this.script_el),e[n]=void 0,!0},insert:function(){function e(e,t,i){var o=e.addEventListener?"addEventListener":"attachEvent",n=e.addEventListener?"":"on";e[o](n+t,i,!1)}this.element_mask=this.build_viewport_mask();var t=this.options.zindex;this.element_mask&&(t+=1);var i=o.createElement("div");i.className="cookiebanner",i.style.position="fixed",i.style.left=0,i.style.right=0,i.style.height=this.options.height,i.style.minHeight=this.options.minHeight,i.style.zIndex=t,i.style.background=this.options.bg,i.style.color=this.options.fg,i.style.lineHeight=i.style.minHeight,i.style.padding="5px 16px",i.style.fontFamily=this.options.fontFamily,i.style.fontSize=this.options.fontSize,i.style.textAlign=this.options.textAlign,"top"===this.options.position?i.style.top=0:i.style.bottom=0;var n='
    '+this.options.closeText+"
    ",s=""+this.options.message+(this.options.linkmsg?" "+this.options.linkmsg+"":"")+"";this.options.closePrecedes?i.innerHTML=n+s:i.innerHTML=s+n,this.element=i;var a=i.getElementsByTagName("a")[0];a.href=this.options.moreinfo,a.target=this.options.moreinfoTarget,this.options.moreinfoRel&&""!==this.options.moreinfoRel&&(a.rel=this.options.moreinfoRel),a.style.textDecoration=this.options.moreinfoDecoration,a.style.color=this.options.link,a.style.fontWeight=this.options.moreinfoFontWeight,""!==this.options.moreinfoFontSize&&(a.style.fontSize=this.options.moreinfoFontSize);var c=i.getElementsByTagName("div")[0];c.style.cursor="pointer";var l=this;e(c,"click",function(){l.agree_and_close()}),this.element_mask&&(e(this.element_mask,"click",function(){l.agree_and_close()}),o.body.appendChild(this.element_mask)),this.options.acceptOnScroll&&e(window,"scroll",function(){l.agree_and_close()}),this.options.acceptOnClick&&e(window,"click",function(){l.agree_and_close()}),this.options.acceptOnTimeout&&!isNaN(parseFloat(this.options.acceptOnTimeout))&&isFinite(this.options.acceptOnTimeout)&&setTimeout(function(){l.agree_and_close()},this.options.acceptOnTimeout),this.options.acceptOnFirstVisit&&l.agree(),o.body.appendChild(this.element),this.inserted=!0,"fade"===this.options.effect?(this.element.style.opacity=0,r.fade_in(this.element)):this.element.style.opacity=1}},a&&(e[n]||(e[n]=new c))}(window); 3 | -------------------------------------------------------------------------------- /public/polityka-prywatnosci.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | Polityka prywatności 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 45 | 46 |
    47 |
    48 |
    49 |

    Polityka prywatności

    50 | 51 | Wersja PDF 52 |
    53 |
    54 |
    55 | 63 | 64 |
    65 |

    66 | 67 |

    Jeżeli tutaj trafiłeś, to niezawodny znak, że cenisz swoją prywatność. Doskonale to rozumiem, dlatego przygotowałem dla Ciebie ten dokument, w którym znajdziesz zasady przetwarzania danych osobowych oraz wykorzystywania plików cookies w związku z korzystaniem ze strony internetowej https://tutorials.Comandeer.pl.

    68 |

    Informacja formalna na początek – administratorem strony jestem ja, Tomasz Jakut.

    69 |

    W razie jakichkolwiek wątpliwości związanych z polityką prywatności, w każdej chwili możesz się ze mną skontaktować, wysyłając wiadomość na adres comandeer@comandeer.pl.

    70 |

    Skrócona wersja – najważniejsze informacje

    71 |

    Dbam o Twoją prywatność, ale również o Twój czas. Dlatego przygotowałem dla Ciebie skróconą wersję najważniejszych zasad związanych z ochroną prywatności.

    72 |
      73 |
    • Kontaktując się ze mną, przekazujesz mi swoje dane osobowe, a ja gwarantuję Ci, że Twoje dane pozostaną poufne, bezpieczne i nie zostaną udostępnione jakimkolwiek podmiotom trzecim bez Twojej wyraźnej zgody.
    • 74 |
    • Powierzam przetwarzanie danych osobowych tylko sprawdzonym i zaufanym podmiotom świadczącym usługi związane z przetwarzaniem danych osobowych.
    • 75 |
    • Korzystam z narzędzi analitycznych Google Analytics, które zbierają informacje na temat Twoich odwiedzin strony, takie jak podstrony, które wyświetliłeś, czas, jaki spędziłeś na stronie czy przejścia pomiędzy poszczególnymi podstronami. W tym celu wykorzystywane są pliki cookies firmy Google LLC dotyczące usługi Google Analytics.
    • 76 |
    • Osadzam na stronie nagrania wideo z serwisu YouTube. Gdy odtwarzasz takie nagrania, wykorzystywane są pliki cookies firmy Google LLC dotyczące usługi YouTube.
    • 77 |
    • Zapewniam możliwość korzystania z funkcji społecznościowych, takich jak udostępnianie treści w serwisach społecznościowych oraz subskrypcja profilu społecznościowego. Korzystanie z tych funkcji wiąże się z wykorzystywaniem plików cookies administratorów serwisów społecznościowych takich jak Facebook, Instagram, YouTube, Twitter, Google+, LinkedIN.
    • 78 |
    • Wykorzystuję pliki cookies własne w celu prawidłowego działania strony.
    • 79 |
    80 |

    Jeżeli powyższe informacje nie są dla Ciebie wystarczające, poniżej znajdziesz szczegóły.

    81 |

    Dane osobowe

    82 |

    Administratorem Twoich danych osobowych w rozumieniu przepisów o ochronie danych osobowych jest Tomasz Jakut.

    83 |

    Cele, podstawy prawne oraz okres przetwarzania danych osobowych wskazane są odrębnie w stosunku do każdego celu przetwarzania danych (patrz: opis poszczególnych celów przetwarzania danych osobowych poniżej).

    84 |

    Uprawnienia

    85 |

    RODO przyznaje Ci następujące potencjalne uprawnienia związane z przetwarzaniem Twoich danych osobowych: 86 | [list=1]

    87 |
      88 |
    • prawo dostępu do danych osobowych,
    • 89 |
    • prawo do sprostowania danych osobowych,
    • 90 |
    • prawo do usunięcia danych osobowych,
    • 91 |
    • prawo do ograniczenia przetwarzania danych osobowych,
    • 92 |
    • prawo do wniesienia sprzeciwu co do przetwarzania danych osobowych,
    • 93 |
    • prawo do przenoszenia danych,
    • 94 |
    • prawo do wniesienia skargi do organu nadzorczego,
    • 95 |
    • prawo do odwołania zgody na przetwarzanie danych osobowych, jeżeli takową zgodę wyraziłeś.
    • 96 |
    97 |

    Zasady związane z realizacją wskazanych uprawnień zostały opisane szczegółowo w art. 16–21 RODO. Zachęcam do zapoznania się z tymi przepisami. Ze swojej strony uważam za potrzebne wyjaśnić Ci, że wskazane powyżej uprawnienia nie są bezwzględne i nie będą przysługiwać Ci w stosunku do wszystkich czynności przetwarzania Twoich danych osobowych. Dla Twojej wygody dołożyłem starań, by w ramach opisu poszczególnych operacji przetwarzania danych osobowych wskazać na przysługujące Ci w ramach tych operacji uprawnienia.

    98 |

    Podkreślam, że jedno z uprawnień wskazanych powyżej przysługuje Ci zawsze – jeżeli uznasz, że przy przetwarzaniu Twoich danych osobowych dopuściłem się naruszenia przepisów o ochronie danych osobowych, masz możliwość wniesienia skargi do organu nadzorczego (Prezesa Urzędu Ochrony Danych Osobowych).

    99 |

    Zawsze możesz również zwrócić się do mnie z żądaniem udostępnienia Ci informacji o tym, jakie dane na Twój temat posiadamy oraz w jakich celach je przetwarzamy Wystarczy, że wyślesz wiadomość na adres comandeer@comandeer.pl. Dołożyłem jednak wszelkich starań, by interesujące Cię informacje zostały wyczerpująco przedstawione w niniejszej polityce prywatności. Podany powyżej adres e-mail możesz wykorzystać również w razie jakichkolwiek pytań związanych z przetwarzaniem Twoich danych osobowych.

    100 |

    Bezpieczeństwo

    101 |

    Gwarantuję Ci poufność wszelkich przekazanych nam danych osobowych. Zapewniam podjęcie wszelkich środków bezpieczeństwa i ochrony danych osobowych wymaganych przez przepisy o ochronie danych osobowych. Dane osobowe są gromadzone z należytą starannością i odpowiednio chronione przed dostępem do nich przez osoby do tego nieupoważnione.

    102 |

    Cele i czynności przetwarzania

    103 |

    Kontakt e-mailowy

    104 |

    Kontaktując się ze mną za pośrednictwem poczty elektronicznej, w tym również przesyłając zapytanie poprzez formularz kontaktowy, w sposób naturalny przekazujesz mi swój adres e-mail jako adres nadawcy wiadomości. Ponadto, w treści wiadomości możesz zawrzeć również inne dane osobowe.

    105 |

    Twoje dane są w tym przypadku przetwarzane w celu kontaktu z Tobą, a podstawą przetwarzania jest art. 6 ust. 1 lit. a RODO, czyli Twoja zgoda wynikające z zainicjowania z nami kontaktu. Podstawą prawną przetwarzania po zakończeniu kontaktu jest usprawiedliwiony cel w postaci archiwizacji korespondencji na potrzeby wewnętrzne (art. 6 ust. 1 lit. c RODO).

    106 |

    Treść korespondencji może podlegać archiwizacji i nie jestem w stanie jednoznacznie określić, kiedy zostanie usunięta. Masz prawo do domagania się przedstawienia historii korespondencji, jaką ze mną prowadziłeś (jeżeli podlegała archiwizacji), jak również domagać się jej usunięcia, chyba że jej archiwizacja jest uzasadniona z uwagi na moje nadrzędne interesy, np. obrona przed potencjalnymi roszczeniami z Twojej strony.

    107 |

    Pliki cookies i inne technologie śledzące

    108 |

    Moja strona, podobnie jak niemal wszystkie inne strony internetowe, wykorzystuje pliki cookies, by zapewnić Ci najlepsze doświadczenia związane z korzystaniem z niej.

    109 |

    Cookies to niewielkie informacje tekstowe, przechowywane na Twoim urządzeniu końcowym (np. komputerze, tablecie, smartfonie), które mogą być odczytywane przez nasz system teleinformatyczny.

    110 |

    Cookies można podzielić na cookies własne oraz cookie podmiotów trzecich.

    111 |

    Więcej szczegółów znajdziesz poniżej.

    112 |

    Zgoda na cookies

    113 |

    Podczas pierwszej wizyty na stronie wyświetlana jest Ci informacja na temat stosowania plików cookies wraz z pytaniem o zgodę na wykorzystywanie plików cookies. Zawsze możesz zmienić ustawienia cookies z poziomu swojej przeglądarki albo w ogóle usunąć pliki cookies. Pamiętaj jednak, że wyłączenie plików cookies może powodować trudności w korzystaniu ze strony, jak również z wielu innych stron internetowych, które stosują cookies.

    114 |

    Cookies własne

    115 |

    Cookies własne wykorzystuję w celu zapewnienia prawidłowego działania strony, w szczególności w celu dostosowania layoutu strony do Twoich preferencji.

    116 |

    Cookies podmiotów trzecich

    117 |

    Moja strona, podobnie jak większość współczesnych stron internetowych, wykorzystuje funkcje zapewniane przez podmioty trzecie, co wiąże się z wykorzystywaniem plików cookies pochodzących od podmiotów trzecich. Wykorzystanie tego rodzaju plików cookies zostało opisane poniżej.

    118 |

    Analiza i statystyka

    119 |

    Wykorzystuję cookies do śledzenia statystyk strony, takich jak liczba osób odwiedzających, rodzaj systemu operacyjnego i przeglądarki internetowej wykorzystywanej do przeglądania strony, czas spędzony na stronie, odwiedzone podstrony etc. Korzystam w tym zakresie z Google Analytics, co wiąże się z wykorzystaniem plików cookies firmy Google LLC.

    120 |

    Narzędzia społecznościowe

    121 |

    Zapewniam możliwość korzystania z funkcji społecznościowych, takich jak udostępnianie treści w serwisach społecznościowych oraz subskrypcja profilu społecznościowego. Korzystanie z tych funkcji wiąże się z wykorzystywaniem plików cookies administratorów serwisów społecznościowych takich jak Facebook, Instagram, YouTube, Twitter, Google+, LinkedIN.

    122 |

    Osadzam na stronie nagrania wideo z serwisu YouTube. Gdy odtwarzasz takie nagrania, wykorzystywane są pliki cookies firmy Google LLC dotyczące usługi YouTube.

    123 | 124 |
    125 |
    126 |
    127 | 128 |
    129 |
    130 |

    Copyright © by .

    131 |
    132 |
    133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /public/polityka-prywatnosci.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/polityka-prywatnosci.pdf -------------------------------------------------------------------------------- /public/polymer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/polymer.pdf -------------------------------------------------------------------------------- /public/res/html5-blog/ferrante-book.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/res/html5-blog/ferrante-book.pdf -------------------------------------------------------------------------------- /public/res/html5-blog/final.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Super hiper ważny wpis | Example.net - fajowy blog, na którym bloguję 33 | 34 | 37 | 38 | 39 | Przejdź do treści 40 | 41 |
    42 | 55 |
    56 |

    57 | 58 | 59 | 60 |

    61 |
    62 |
    63 | 64 |
    65 |
    66 |
    67 |

    68 | Super hiper ważny wpis 69 |

    70 | 71 |
    72 |
    73 |
    74 | 75 | 76 | 77 | 78 | Kwadratowa ramka z czerwonym krzyżykiem w środku 79 | 80 |
    Bardzo ważny obrazek[…],który jest bardzo ważny
    81 |
    82 |

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu justo enim, ac faucibus massa. Vestibulum in elit aliquam purus sollicitudin adipiscing ac et sapien. Curabitur eleifend justo diam, et viverra nisi. Quisque a ipsum vehicula nunc vestibulum posuere. Suspendisse potenti. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum porttitor, neque eu congue fermentum, velit ante pellentesque arcu, a posuere risus tortor vel ante. Donec et neque at odio hendrerit mattis sed eu tellus. Nullam accumsan leo ut felis suscipit vehicula posuere erat eleifend. Donec semper lorem eu nibh tincidunt varius.

    83 |

    Podtytuł

    84 |

    Vivamus leo arcu, convallis id iaculis sit amet, fringilla et nunc. Donec aliquam, justo at mollis porta, enim lorem tempor eros, id iaculis arcu elit ac sem. In et erat eu metus ornare vestibulum non at arcu. Integer in nisi massa. Etiam nulla felis, rhoncus non pharetra sed, vehicula eu risus. Vestibulum sodales nunc nisi, vitae gravida nisl. Quisque lacus ipsum, commodo ac hendrerit vitae, porta dapibus tortor. Mauris sed risus diam. Morbi lacus elit, euismod dapibus interdum id, fermentum et mi. Nam at neque orci. Sed ac hendrerit augue. Vestibulum pharetra mattis mattis. Sed porta turpis dolor, condimentum blandit libero. Aliquam non nisi mi. Nulla nec sem elit. Duis blandit viverra lacus, in convallis mauris dictum et. Ut eget risus enim. Suspendisse potenti. Aenean leo odio, luctus quis eleifend quis, consectetur sit amet ipsum. Curabitur commodo leo ac risus dignissim at porttitor turpis fringilla.

    85 |

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ullamcorper neque a magna sodales sodales. Nulla id enim sem, sit amet ultrices dolor. Vestibulum sollicitudin dolor quis quam vehicula egestas. Ut urna erat, gravida a tristique non, congue nec elit. Aenean tincidunt purus sed nisl semper sagittis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus in orci sit amet ligula posuere mollis a in diam. Nam enim tortor, ultrices quis laoreet nec, egestas non leo. Nam non fringilla turpis. Quisque vitae pellentesque nibh. Etiam dictum tincidunt ultricies. Cras sit amet aliquam odio. Morbi quis urna a nulla egestas placerat sed in lacus. Morbi id urna lacinia nunc bibendum ultricies. In pretium leo non odio feugiat porttitor. Donec aliquet purus in purus pretium aliquam. Etiam aliquet nibh ut nibh semper egestas. Cras in purus quam, nec posuere diam. Aliquam erat volutpat.

    86 |

    Morbi porta blandit nisi at fermentum. Praesent vel sagittis urna. Pellentesque ipsum eros, vestibulum id gravida vitae, dictum vel sapien. Nam placerat lacus non lacus facilisis vitae hendrerit nibh adipiscing. Sed aliquet nisi ligula, eu viverra turpis. Curabitur tincidunt arcu in enim tempus sit amet auctor nunc luctus. Praesent dui turpis, lobortis in euismod quis, fringilla at felis. Nullam ligula purus, mattis nec tristique non, aliquam vitae sem. Quisque sit amet magna magna, eget aliquam orci. Aliquam placerat diam sit amet erat gravida fermentum. Maecenas malesuada nibh quis lorem pellentesque laoreet. Vestibulum pulvinar tincidunt fringilla. Nullam pellentesque felis ac nisi dictum egestas. Nunc metus mi, euismod non convallis nec, varius et felis. Pellentesque vel lacus turpis. In hac habitasse platea dictumst. Morbi sodales mauris a nisi feugiat pulvinar.

    87 |

    Vivamus vitae lectus ut dui luctus iaculis. Pellentesque quis lectus id ipsum venenatis bibendum. Pellentesque non magna nec purus mollis suscipit. Nulla vestibulum interdum ante, vestibulum ullamcorper est vulputate vitae. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque aliquet mollis rhoncus. Phasellus imperdiet posuere bibendum. Quisque consequat imperdiet nibh vitae pretium. Suspendisse quis eros libero, non luctus mauris. Nulla laoreet nibh at eros scelerisque condimentum. Cras aliquam velit at orci luctus et tincidunt arcu scelerisque. Donec at quam magna. Integer leo leo, ultrices facilisis bibendum vel, feugiat interdum neque. Fusce vitae nibh sapien, sit amet consequat velit. Fusce adipiscing tortor id nibh fringilla sollicitudin. Donec sodales sapien in sapien rutrum rutrum. Nam quis enim nec nibh varius mattis nec in ante. Morbi non est eu diam elementum interdum. Morbi at odio non libero tempus eleifend nec quis ligula. Cras pellentesque mauris sit amet felis vestibulum tincidunt.

    88 |
    89 |
    90 |
    91 |

    Tagi

    92 |
      93 |
    • 94 | 95 |
    • 96 |
    • 97 | 98 |
    • 99 |
    100 |
    101 |
    102 | 103 |
    104 |

    Komentarze

    105 |
      106 |
    1. 107 |
      108 | 109 | 110 | 111 | Comandeer skomentował 112 |
      113 |

      Ale komentarz!

      114 |
    2. 115 |
    3. 116 |
      117 | 118 | 119 | 120 | Anonim skomentował 121 |
      122 |

      Inny komentarz!

      123 |
    4. 124 |
    125 | 126 |
    127 |

    Dodaj komentarz

    128 |

    129 | 130 | 131 |

    132 |

    133 | 134 | 135 |

    136 |

    137 | 138 | 139 |

    140 |

    141 | 142 | 143 |

    144 |

    145 | 146 |

    147 | 148 |
    149 |
    150 |
    151 |
    152 | 153 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /public/res/html5-blog/outlinehtml4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/res/html5-blog/outlinehtml4.png -------------------------------------------------------------------------------- /public/res/html5-blog/outlinehtml5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/res/html5-blog/outlinehtml5.png -------------------------------------------------------------------------------- /public/res/html5-blog/start.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Super hiper ważny wpis | Example.net - fajowy blog, na którym bloguję 17 | 18 | 19 | 39 |
    40 |
    41 |

    42 | Super hiper ważny wpis 43 |

    44 |

    Opublikowano 07.01.2011 przez Comandeer

    45 | Ważny obrazek 46 |

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu justo enim, ac faucibus massa. Vestibulum in elit aliquam purus sollicitudin adipiscing ac et sapien. Curabitur eleifend justo diam, et viverra nisi. Quisque a ipsum vehicula nunc vestibulum posuere. Suspendisse potenti. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum porttitor, neque eu congue fermentum, velit ante pellentesque arcu, a posuere risus tortor vel ante. Donec et neque at odio hendrerit mattis sed eu tellus. Nullam accumsan leo ut felis suscipit vehicula posuere erat eleifend. Donec semper lorem eu nibh tincidunt varius.

    47 |

    Vivamus leo arcu, convallis id iaculis sit amet, fringilla et nunc. Donec aliquam, justo at mollis porta, enim lorem tempor eros, id iaculis arcu elit ac sem. In et erat eu metus ornare vestibulum non at arcu. Integer in nisi massa. Etiam nulla felis, rhoncus non pharetra sed, vehicula eu risus. Vestibulum sodales nunc nisi, vitae gravida nisl. Quisque lacus ipsum, commodo ac hendrerit vitae, porta dapibus tortor. Mauris sed risus diam. Morbi lacus elit, euismod dapibus interdum id, fermentum et mi. Nam at neque orci. Sed ac hendrerit augue. Vestibulum pharetra mattis mattis. Sed porta turpis dolor, condimentum blandit libero. Aliquam non nisi mi. Nulla nec sem elit. Duis blandit viverra lacus, in convallis mauris dictum et. Ut eget risus enim. Suspendisse potenti. Aenean leo odio, luctus quis eleifend quis, consectetur sit amet ipsum. Curabitur commodo leo ac risus dignissim at porttitor turpis fringilla.

    48 |

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ullamcorper neque a magna sodales sodales. Nulla id enim sem, sit amet ultrices dolor. Vestibulum sollicitudin dolor quis quam vehicula egestas. Ut urna erat, gravida a tristique non, congue nec elit. Aenean tincidunt purus sed nisl semper sagittis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus in orci sit amet ligula posuere mollis a in diam. Nam enim tortor, ultrices quis laoreet nec, egestas non leo. Nam non fringilla turpis. Quisque vitae pellentesque nibh. Etiam dictum tincidunt ultricies. Cras sit amet aliquam odio. Morbi quis urna a nulla egestas placerat sed in lacus. Morbi id urna lacinia nunc bibendum ultricies. In pretium leo non odio feugiat porttitor. Donec aliquet purus in purus pretium aliquam. Etiam aliquet nibh ut nibh semper egestas. Cras in purus quam, nec posuere diam. Aliquam erat volutpat.

    49 |

    Morbi porta blandit nisi at fermentum. Praesent vel sagittis urna. Pellentesque ipsum eros, vestibulum id gravida vitae, dictum vel sapien. Nam placerat lacus non lacus facilisis vitae hendrerit nibh adipiscing. Sed aliquet nisi ligula, eu viverra turpis. Curabitur tincidunt arcu in enim tempus sit amet auctor nunc luctus. Praesent dui turpis, lobortis in euismod quis, fringilla at felis. Nullam ligula purus, mattis nec tristique non, aliquam vitae sem. Quisque sit amet magna magna, eget aliquam orci. Aliquam placerat diam sit amet erat gravida fermentum. Maecenas malesuada nibh quis lorem pellentesque laoreet. Vestibulum pulvinar tincidunt fringilla. Nullam pellentesque felis ac nisi dictum egestas. Nunc metus mi, euismod non convallis nec, varius et felis. Pellentesque vel lacus turpis. In hac habitasse platea dictumst. Morbi sodales mauris a nisi feugiat pulvinar.

    50 |

    Vivamus vitae lectus ut dui luctus iaculis. Pellentesque quis lectus id ipsum venenatis bibendum. Pellentesque non magna nec purus mollis suscipit. Nulla vestibulum interdum ante, vestibulum ullamcorper est vulputate vitae. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque aliquet mollis rhoncus. Phasellus imperdiet posuere bibendum. Quisque consequat imperdiet nibh vitae pretium. Suspendisse quis eros libero, non luctus mauris. Nulla laoreet nibh at eros scelerisque condimentum. Cras aliquam velit at orci luctus et tincidunt arcu scelerisque. Donec at quam magna. Integer leo leo, ultrices facilisis bibendum vel, feugiat interdum neque. Fusce vitae nibh sapien, sit amet consequat velit. Fusce adipiscing tortor id nibh fringilla sollicitudin. Donec sodales sapien in sapien rutrum rutrum. Nam quis enim nec nibh varius mattis nec in ante. Morbi non est eu diam elementum interdum. Morbi at odio non libero tempus eleifend nec quis ligula. Cras pellentesque mauris sit amet felis vestibulum tincidunt.

    51 |
    52 |
    53 |

    Tagi

    54 |
      55 |
    1. 56 | Tag1 57 |
    2. 58 |
    3. 59 | Tag2 60 |
    4. 61 |
    62 |
    63 |

    Komentarze

    64 |
      65 |
    1. 66 | 30.12.08, 21:19 67 | Avatar użytkownika ComandeerComandeer 68 |
      69 |

      Ale komentarz!

      70 |
      71 |
    2. 72 |
    73 |

    Dodaj komentarz

    74 |
    75 |
    76 | Napisz komentarz 77 |
    78 |
    79 | 80 |
    81 |
    82 | 83 |
    84 |
    85 | 86 |
    87 |
    88 | 89 |
    90 |
    91 | 92 |
    93 |
    94 | 95 |
    96 |
    97 | 98 |
    99 |
    100 | 101 |
    102 |
    103 | 104 |
    105 |
    106 | 107 |
    108 |
    109 |
    110 |
    111 |
    112 |

    O autorze blogaska

    113 |

    Nazywam się Comandeer, mieszkam w uroczym miasteczku Świętochłowice i interesuję się webmasterką.

    114 |
    115 |
    116 |

    Tagi

    117 |
      118 |
    • 119 | Tag1 120 |
    • 121 |
    • 122 | Tag2 123 |
    • 124 |
    • 125 | Tag3 126 |
    • 127 |
    • 128 | Tag4 129 |
    • 130 |
    131 |
    132 |
    133 |

    Archiwum

    134 |
      135 |
    1. 136 | Styczeń 2011 137 |
    2. 138 |
    3. 139 | Grudzień 2010 140 |
    4. 141 |
    142 |
    143 |
    144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /public/res/svg-sprites/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ehhh... 6 | 20 | 21 | 22 |
    23 |

    24 | 25 | -------------------------------------------------------------------------------- /public/res/svg-sprites/stack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | user-agent: * 2 | disallow: /res/ 3 | -------------------------------------------------------------------------------- /public/svg-sprites.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | [CSS] Sprite'y w SVG 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 45 | 46 |
    47 |
    48 |
    49 |

    [CSS] Sprite'y w SVG

    50 | 51 | Wersja PDF 52 |
    53 |
    54 |
    55 | 63 | 64 |
    65 |

    66 | 67 |
    68 |

    Ten tutorial powstał bardzo dawno temu i od tego czasu zmieniło się sporo! Polecam artykuł na CSS-Tricks.com, opisujący temat o wiele dokładniej.

    69 |
    70 |

    Czasami sprite'y w CSS mogą być wkurzające

    71 |
    .help
     72 | {
     73 | 	background: url(super-hiper-sprite.png) no-repeat -567px -234px;
     74 | }
     75 | 
    76 |

    Jednak ostatnio ktoś wpadł na genialny pomysł: zróbmy to w SVG!

    77 |

    Na czym polega cały trick? Otóż chodzi o zapisanie w jednym pliku SVG kilku ikonek. Do tego dodano trochę magii z CSS3, aby tylko potrzebna nam ikonka się pokazywała. Ta magia jest już dobrze znana:

    78 |
    .icon {display:none;}
     79 | .icon:target {display:block;}
     80 | 
    81 |

    Stwórzmy sobie zatem prosty pliczek SVG:

    82 |
    <?xml version="1.0" encoding="utf-8"?>
     83 | <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
     84 | 	<defs>
     85 | 		<style>
     86 | 			.icon
     87 | 			{
     88 | 				display: none;
     89 | 			}
     90 | 			.icon:target
     91 | 			{
     92 | 				display: inline;
     93 | 			}
     94 | 		</style>
     95 | 	</defs>
     96 | 	<g id="rect" class="icon">
     97 | 	<rect x="8" width="2" height="16"/>
     98 | 	<rect y="8" width="16" height="2" />
     99 | 	</g>
    100 | 	<g id="line" class="icon">
    101 | 		<line x1="0" y1="0" x2="16" y2="16" stroke="blue" stroke-width="1" />
    102 | 		<line x1="0" y1="0" x2="0" y2="16" stroke="red" stroke-width="1" />
    103 | 	</g>
    104 | </svg>
    105 | 
    106 |

    najwyższych lotów to on nie jest, wiem, ale do prezentacji starczy ;)

    107 |

    Ot, nic nadzwyczajnego - mamy dwie ikonki: jakieś dwa prostokąciki i dwie linie. Jak widać, początkowo obydwie są ukryte, ale :target działa :D Ważna dla nas jest klasa .icon i id poszczególnych ikonek (do nich się będziemy odwoływać). Można oczywiście jeszcze bardziej uprościć kod i pozbyć się klasy .icon, posługując się selektorem g.

    108 |

    W HTML i CSS też wielkiego problemu nie ma:

    109 |
    <style>
    110 | .icon.rect
    111 | {
    112 | 	width: 32px;
    113 | 	height: 32px;
    114 | 	background: url(stack.svg#rect) no-repeat;
    115 | }
    116 | </style>
    117 | <button class="icon rect"></button>
    118 | 
    119 |

    Ot, zwykłe tło w formacie SVG... I tu pojawia się problem, bo technika ta działa tylko w Firefoxie, co mnie osobiście dziwi (ej, w końcu SVG to wieloletni standard de facto!). Na szczęście wkrótce wsparcie ma się pojawić także w Operze i reszcie.

    120 |

    Technika IMO bardzo pomocna i obiecująca. Nie dość, że łatwiej się nią posługiwać (nie trzeba wyliczać pozycji tła), to jeszcze obrazki automatycznie się skalują do wielkości elementu (można to sprawdzić, zmieniając wielkość naszego przycisku). No i teoretycznie powinno to ważyć mniej niż zwykły obrazek ;)

    121 |

    [size=14]DEMO[/size]

    122 |
    123 |

    Artykuł można potraktować jako bardzo bardzo luźne tłumaczenie notki Simuraia. W porównaniu do orginalnej metody, lekko uprościłem kod SVG.

    124 |
    125 | 126 |
    127 |
    128 |
    129 | 130 |
    131 |
    132 |

    Copyright © by .

    133 |
    134 |
    135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /public/svg-sprites.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Comandeer/comandeers-tutorials/44bca507f4fc263b43f22a347a49fe5bf5312a3e/public/svg-sprites.pdf --------------------------------------------------------------------------------