-
Notifications
You must be signed in to change notification settings - Fork 0
/
feed.xml
1286 lines (977 loc) · 123 KB
/
feed.xml
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en_GB"><generator uri="https://jekyllrb.com/" version="4.2.2">Jekyll</generator><link href="https://vtvz.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://vtvz.me/" rel="alternate" type="text/html" hreflang="en_GB" /><updated>2024-09-10T23:02:58+03:00</updated><id>https://vtvz.me/feed.xml</id><title type="html">Vitaly Zaslavsky – DevOps Engineer, who loves his job</title><subtitle>DevOps engineer's personal blog about everything. Notes on successes and failures in professional activities, about finding solutions to practical problems, and just about life.
</subtitle><author><name>Vitaly Zaslavsky</name><email>vtvz@pm.me</email></author><entry xml:lang="en"><title type="html">Solving eCryptfs “Failed to mount private data” error</title><link href="https://vtvz.me/blog/ecryptfs-recovery-error/" rel="alternate" type="text/html" title="Solving eCryptfs “Failed to mount private data” error" /><published>2022-06-26T00:00:00+03:00</published><updated>2022-06-26T00:00:00+03:00</updated><id>https://vtvz.me/blog/ecryptfs-recovery-error</id><content type="html" xml:base="https://vtvz.me/blog/ecryptfs-recovery-error/"><![CDATA[<p>One day I decided to try Manjaro out. My laptop is pretty good-ish, and one of my requirements is an encrypted home directory. On the previous installation on Ubuntu 20.04, I used <code class="language-plaintext highlighter-rouge">ecryptfs</code> and it was good enough for my needs. When I moved to Manjaro I decided to leave <code class="language-plaintext highlighter-rouge">ecryptfs</code> as my primary encryption method but to recreate the passphrase from scratch with a completely fresh and clean home folder. Then I moved all encrypted files to another location… I believe this was the right decision, but I struggled for about a day to restore access to my previous data. This little note is for me and for those who need a bit of help with the recovery process.</p>
<!--cut-->
<blockquote>
<p>For those who just came for the <span class="text-muted">possible</span> solution, jump straight to <a href="#tldr">TL;DR</a></p>
</blockquote>
<h2 id="the-problem-i-faced-with">The problem I faced with</h2>
<p>If you are here you should already know that the encrypted data is located in <span class="text-nowrap"><code class="language-plaintext highlighter-rouge">/home/.ecryptfs/%username%</code></span>. I moved my previous data to <code class="language-plaintext highlighter-rouge">/home/.ecryptfs/prev</code> but it doesn’t really matter. You can replace <code class="language-plaintext highlighter-rouge">%username%</code> or whole path with your value. So let’s replace the whole <code class="language-plaintext highlighter-rouge">/home/.ecryptfs/%username%</code> with short <code class="language-plaintext highlighter-rouge">%path%</code> for the sake of brevity.</p>
<p>I tried to use <code class="language-plaintext highlighter-rouge">ecryptfs-recover-private</code> and got this error:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>ecryptfs-recover-private %path%/.Private
<span class="c">### Output ###</span>
<span class="c"># INFO: Found [%path%/.Private].</span>
<span class="c"># Try to recover this directory? [Y/n]: </span>
<span class="c"># INFO: Found your wrapped-passphrase</span>
<span class="c"># Do you know your LOGIN passphrase? [Y/n] </span>
<span class="c"># INFO: Enter your LOGIN passphrase...</span>
<span class="c"># Passphrase: </span>
<span class="c"># Inserted auth tok with sig [3121cedc8cecfb1] into the user session keyring</span>
<span class="c"># mount: /tmp/ecryptfs.xxxxxxxx: mount(2) system call failed: No such file or directory.</span>
<span class="c"># dmesg(1) may have more information after failed mount system call.</span>
<span class="c"># ERROR: Failed to mount private data at [/tmp/ecryptfs.xxxxxxxx].</span>
</code></pre></div></div>
<p><span class="text-muted">
(I use <code class="language-plaintext highlighter-rouge">sudo</code> instead of <code class="language-plaintext highlighter-rouge">#</code> because syntax highlighting messes up with hashtag)
</span></p>
<p>Wierd…</p>
<h2 id="adventure-on-the-way-to-the-solution">Adventure on the way to the solution</h2>
<p>So it says <code class="language-plaintext highlighter-rouge">dmesg</code> can help. Let’s try. It’s a bit hard to find the necessary information in this constant flow of messages… I tried to run these two commands one right after the other. And there’s a hint:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dmesg
<span class="c">### Output ###</span>
<span class="c"># [128513.402407] Could not find key with description: [2a283744646d8d1]</span>
<span class="c"># [128513.402410] process_request_key_err: No key</span>
<span class="c"># [128513.402411] Could not find valid key in user session keyring for sig specified in mount option: [2a283744646d8d1]</span>
</code></pre></div></div>
<p>Yeah, <a href="https://www.google.com/search?q=I+know+some+of+these+words">I know some of these words</a>. I guess the essential part is <code class="language-plaintext highlighter-rouge">user session keyring</code></p>
<p>My craziest thought was “why not try to add some key in some keyring of the current user”. So I did this (after a lot of googling):</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ecryptfs-unwrap-passphrase %path%/.ecryptfs/wrapped-passphrase
<span class="c">### Output ###</span>
<span class="c"># Passphrase: </span>
<span class="c"># %32-long-line-with-random-chars%</span>
</code></pre></div></div>
<p>Instead of <code class="language-plaintext highlighter-rouge">%32-long-line-with-random-chars%</code>, you will get your own key.</p>
<p>Next I found this line:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="nt">-n</span> <span class="s2">"%32-long-line-with-random-chars%"</span> | ecryptfs-add-passphrase <span class="nt">--fnek</span> -
<span class="c">### Output ###</span>
<span class="c"># Inserted auth tok with sig [3121cedc8cecfb1] into the user session keyring</span>
<span class="c"># Inserted auth tok with sig [2a283744646d8d1] into the user session keyring</span>
</code></pre></div></div>
<p>And now tried mount private folder again:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>ecryptfs-recover-private %path%/.Private
<span class="c">### Output ###</span>
<span class="c"># [sudo] password for username: </span>
<span class="c"># INFO: Found [%path%/.Private].</span>
<span class="c"># Try to recover this directory? [Y/n]: </span>
<span class="c"># INFO: Found your wrapped-passphrase</span>
<span class="c"># Do you know your LOGIN passphrase? [Y/n] </span>
<span class="c"># INFO: Enter your LOGIN passphrase...</span>
<span class="c"># Passphrase: </span>
<span class="c"># Inserted auth tok with sig [3121cedc8cecfb1] into the user session keyring</span>
<span class="c"># INFO: Success! Private data mounted at [/tmp/ecryptfs.yyyyyyyy].</span>
</code></pre></div></div>
<p>And voilà! I have my data back:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-al</span> /tmp/ecryptfs.yyyyyyyy
<span class="c">### Output ###</span>
<span class="c"># drwx------ 3 username username 28672 июн 12 15:17 .</span>
<span class="c"># drwxrwxrwt 26 root root 1360 июн 23 23:04 ..</span>
<span class="c"># drwxrwxr-x 3 username username 4096 янв 5 2021 .local</span>
</code></pre></div></div>
<p>Yeah, there were a lot more things in it. But I’ve already moved it away :grin:</p>
<hr />
<p>I think this is not a new problem. But I spent a lot of time solving this puzzle, so I decided to share my knowledge for future me.</p>
<h2 id="tldr">TL;DR</h2>
<p>If you have this error trying <code class="language-plaintext highlighter-rouge">ecryptfs-recover-private</code>:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mount: /tmp/ecryptfs.xxxxxxxx: mount(2) system call failed: No such file or directory.
dmesg(1) may have more information after failed mount system call.
ERROR: Failed to mount private data at [/tmp/ecryptfs.xxxxxxxx].
</code></pre></div></div>
<p>You can try this:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ecryptfs-unwrap-passphrase %path%/.ecryptfs/wrapped-passphrase
<span class="c"># Shouldn't be sudo!</span>
<span class="c"># Replace "%32-long-line-with-random-chars%" with `ecryptfs-unwrap-passphrase` output</span>
<span class="nb">echo</span> <span class="nt">-n</span> <span class="s2">"%32-long-line-with-random-chars%"</span> | ecryptfs-add-passphrase <span class="nt">--fnek</span> -
<span class="nb">sudo </span>ecryptfs-recover-private %path%/.Private
</code></pre></div></div>
<p>If you succeed, please inform me about it somehow. I will be super thankful :blush:</p>
<hr />
<p>P.S. I’m not a native English-speaking person, my knowledge is relatively low. This post is my first in this language and can have a lot, I mean <strong>A LOT</strong> of issues. I will be very thankful if you help me to fix my grammar errors or give me an advice(s) :fox:</p>]]></content><author><name>Vitaly Zaslavsky</name><email>vtvz@pm.me</email></author><category term="Linux" /><category term="eCryptfs" /><category term="Linux" /><summary type="html"><![CDATA[How to deal with ecryptfs error "mount(2) system call failed: No such file or directory. Failed to mount private data at /tmp/ecryptfs"]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vtvz.me/blog/ecryptfs-recovery-error/cover.png" /><media:content medium="image" url="https://vtvz.me/blog/ecryptfs-recovery-error/cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="ru"><title type="html">Meet Rustify! Приглашение на закрытое тестирование</title><link href="https://vtvz.me/blog/meet-rustify/" rel="alternate" type="text/html" title="Meet Rustify! Приглашение на закрытое тестирование" /><published>2022-06-19T00:00:00+03:00</published><updated>2022-06-19T00:00:00+03:00</updated><id>https://vtvz.me/blog/meet-rustify</id><content type="html" xml:base="https://vtvz.me/blog/meet-rustify/"><![CDATA[<figure class="frame post-image post-image--right"><a href="./rustify-logo.png" target="_blank"><img src="./rustify-logo.png" alt="" title="" /></a></figure>
<p>Последние несколько месяцев я в качестве хобби-проекта разрабатывал небольшой телеграм-бот. Не столько из необходимости, сколько из желания выучить новый язык программирования (Rust <i class="fa-brands fa-rust"></i> <i class="fa-solid fa-heart"></i>) и немного улучшить себе жизнь.</p>
<p>На текущий момент бот тесно интегрируется со Spotify и выполняет несколько <small class="text-muted">не очень</small> простых функций:</p>
<ul>
<li>Позволяет дизлайкнуть трек, чтобы никогда его больше не слышать. В случае, если устройство подключено к интернету и аккаунт является премиальным, бот будет просто переключать его на следующий трек. Никаких больше ремиксов Эд Ширана!</li>
<li>Получать более подробную информацию по треку:
<ul>
<li>Различные показали от самого Spotify (всяческие ‘acousticness’, ‘danceability’, ‘energy’, ‘instrumentalness’, а также темп, тональность и прочие, которые я описал, как мог)</li>
<li>Жанры исполнителей трека (жанр самого трека может отличаться).</li>
<li>Текст и язык песни. Spotify сам предоставляет тексты некоторых песен в приложении, но мой бот ищет их еще и на <a href="https://genius.com/">Genius</a>.</li>
</ul>
</li>
<li>Бот следит за тем, что вы слушаете, и сообщает о наличии ненормативной лексики в текстах <strong>англоязычных</strong> песен. И сразу предлагает поставить дизлайк этому треку, либо проигнорировать предупреждение в случае ложно позитивных срабатываний (такое тоже бывает).</li>
</ul>
<p><span class="text-muted">
// О том, как это все работает, я планирую написать отдельный пост
</span></p>
<p>На текущий момент бот в состоянии <abbr title="Minimal Viable Product">MVP</abbr>, в нем не хватает некоторых важных функций (например поставить проверку текстов на паузу), он все еще может падать из-за каких-то ошибок, не хватает нормальной документации и раздела помощи, интерфейс полностью на английском и п.р.</p>
<p>Но он уже нашел и проверил тексты >5600 песен для меня, моей семьи и еще пары людей. На текущий момент я дизлайкнул 755 треков, а бот пропустил ~620 раз <span class="text-muted">(на самом деле больше, просто статистику по пропускам я запустил не так давно)</span></p>
<figure class="frame "><a href="./lyrics-stats.png" target="_blank"><img src="./lyrics-stats.png" alt="" title="" /></a><figcaption><p>Немного закулисной статистики</p></figcaption></figure>
<p>Так вот о чем я. <strong>Мне нужно еще 5-10 человек:</strong></p>
<ul>
<li>У которых есть Telegram и аккаунт Spotify.</li>
<li>Которые на базовом уровне знают английский.</li>
<li>Которым не все равно, что они слушают, и хотят получать уведомления о плохих словах в текстах играющих песен.</li>
<li>Которые хотят кнопку “дизлайк” в Spotify хоть в каком-то виде.</li>
<li>И желательно, чтобы был Spotify Premium.
<span class="text-muted">// Понимаю, что в России это может быть сложно</span>
<ul>
<li>Можно пару человек без премиума. Он нужен только для функции дизлайка.</li>
</ul>
</li>
</ul>
<p><strong>Мне нужно больше добровольцев для тестирования!</strong></p>
<p>На текущий момент у бота 5 активных пользователей и многие проблемы были решены на ранних этапах. Пока я не могу сделать его полностью публичным по различным причинам.</p>
<p>Поэтому, если ты подходишь под требования выше и хочешь помочь:</p>
<ul>
<li>напиши в комментариях к этому посту в блоге,</li>
<li>в комментариях к публикации в telegram</li>
<li>или мне в личные сообщения там же</li>
</ul>
<p>… для получения дальнейших инструкций.</p>
<div class="alert alert-info">
<p>
<strong>ЛИБО!</strong> Можно добавить бота напрямую без необходимости куда-то писать. Ссылка:
</p>
<p>
<a class="social-button social-button--block social-button--telegram" href="https://t.me/RustifyBot">
<i class="fa-fw fa-brands fa-telegram-plane"></i><span class="social-button__label"> RustifyBot</span>
</a>
</p>
</div>
<p>Добро пожаловать всем желающим! :fox:</p>
<p>P.S. Исходный код проекта доступен на <a href="https://github.com/vtvz/rustify">github.com/vtvz/rustify</a></p>]]></content><author><name>Vitaly Zaslavsky</name><email>vtvz@pm.me</email></author><category term="Разработка" /><category term="Telegram" /><category term="TelegramBot" /><category term="Rustify" /><category term="PetProject" /><summary type="html"><![CDATA[Последние несколько месяцев я в качестве хобби-проекта разрабатывал небольшой телеграм-бот. Теперь мне нужны добровольцы для тестирования!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vtvz.me/blog/meet-rustify/cover.png" /><media:content medium="image" url="https://vtvz.me/blog/meet-rustify/cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="ru"><title type="html">Аккуратно внедряем Emoji, не ломая все остальное</title><link href="https://vtvz.me/blog/reliable-emojification/" rel="alternate" type="text/html" title="Аккуратно внедряем Emoji, не ломая все остальное" /><published>2020-05-15T00:00:00+03:00</published><updated>2020-05-15T00:00:00+03:00</updated><id>https://vtvz.me/blog/reliable-emojification</id><content type="html" xml:base="https://vtvz.me/blog/reliable-emojification/"><![CDATA[<p>Emoji стали неотъемлемой частью нашей культуры.
И я их активно использую в своих статьях.
Ранее я использовал плагин для jekyll <a href="https://github.com/jekyll/jemoji">jemoji</a>,
но набор смайликов был весьма скудный, поэтому я отказался от него в пользу <a href="https://github.com/iamcal/js-emoji">js-emoji</a>.
Но и тут не было все гладко…</p>
<!--cut-->
<h2 id="проблема">Проблема</h2>
<p>Сначала эта библиотека загружалась синхронно со всей страницей.
Это значит то, что индикатор загрузки не исчезнет, пока смайлики не отрисуются.
Для рендера использовался простейший скрипт, <a href="https://github.com/iamcal/js-emoji/blob/master/lib/jquery.emoji.js">взятый из официального репозитория</a>,
который выглядел следующим образом:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">$</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">emoji</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">EmojiConvertor</span><span class="p">();</span>
<span class="nx">emoji</span><span class="p">.</span><span class="nx">img_sets</span><span class="p">.</span><span class="nx">apple</span><span class="p">.</span><span class="nx">path</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/assets/images/emoji/</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">emoji</span><span class="p">.</span><span class="nx">include_title</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nx">emoji</span><span class="p">.</span><span class="nx">include_text</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nx">emoji</span><span class="p">.</span><span class="nx">replace_mode</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">css</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">emoji</span><span class="p">.</span><span class="nx">allow_native</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nx">emoji</span><span class="p">.</span><span class="nx">addAliases</span><span class="p">({</span>
<span class="dl">'</span><span class="s1">fox</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1f98a</span><span class="dl">'</span><span class="p">,</span>
<span class="p">});</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">.js-emojify</span><span class="dl">'</span><span class="p">).</span><span class="nx">each</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span><span class="dl">'</span><span class="s1">:not(script)</span><span class="dl">'</span><span class="p">).</span><span class="nx">html</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">oldHtml</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">emoji</span><span class="p">.</span><span class="nx">replace_unified</span><span class="p">(</span><span class="nx">emoji</span><span class="p">.</span><span class="nx">replace_emoticons</span><span class="p">(</span><span class="nx">oldHtml</span><span class="p">));</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Все понятно и очевидно: вытягиваем весь html, вставляем туда иконки, заменяем старый html новым.
И все это работало хорошо… до поры.
Проблемы начались тогда, когда я решил, что смайлики должны отрисовываться <em>после</em> загрузки страницы, а не во время.
Это значительно ускоряло отображение страниц, но создавало некоторые проблемы.</p>
<p>Самая основная выглядела следующим образом:</p>
<ol>
<li>Загружалась страница;</li>
<li>Отрисовывался ReactJS виджет;</li>
<li>Происходил стандартный набор действий:
<ul>
<li>вытягивался весь html;</li>
<li>вставлялись смайлы;</li>
<li>заменялся старый html новым;</li>
</ul>
</li>
</ol>
<p>И после этого всего виджеты переставали работать.
Почему? Потому что мы заменили старый html совершенно новым,
после чего все обработчики форм переставали работать совсем.
Хотя визуально для нас ничего не поменялось,
с точки зрения браузера это совсем другие эллементы.</p>
<p>Искусственно добиться этого эффекта можно следующим образом:</p>
<ul>
<li>Перейти в <a href="/blog/big-changes/">этот пост</a>;</li>
<li>Спуститься до формы и проверить, что все работает;</li>
<li>Открыть “Инструменты разработчика”;</li>
<li>Выполнить:
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#social-post-generator-container</span><span class="dl">'</span><span class="p">).</span><span class="nx">html</span><span class="p">(</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#social-post-generator-container</span><span class="dl">'</span><span class="p">).</span><span class="nx">html</span><span class="p">()</span>
<span class="p">)</span>
</code></pre></div> </div>
<p><span class="text-muted">Таким образом мы заменили оригинальный html новым</span></p>
</li>
<li>Убедиться, что ничего не работает.</li>
</ul>
<p>Еще одна проблема была в том, что он заменял emoji даже в теге <code class="language-plaintext highlighter-rouge">script</code>.
Но это уже не так критично и легко решается.</p>
<h2 id="решение">Решение</h2>
<p>Предположим, что у нас уже есть функция <code class="language-plaintext highlighter-rouge">emojificator</code>, которая добавляет эмотиконы в текст.
Работает она ровно также, как в указанном выше куске кода.</p>
<p>Чего нам нужно добиться:</p>
<ol>
<li>Обновляться должен только текст. Существующие html объекты должны оставаться прежними.</li>
<li>Не трогать <code class="language-plaintext highlighter-rouge">script</code> теги.</li>
<li>Не трогать <code class="language-plaintext highlighter-rouge">textarea</code> и прочие “input” элементы.</li>
</ol>
<p>Мне потребовалось несколько часов на исследование и реализацию этого дела, но результатом я остался доволен.</p>
<h3 id="обход-всех-текстовых-нод">Обход всех текстовых нод</h3>
<p>DOM состоит не только из обычных элементов, но и из текста, комментариев и п.р.
Наша задача состоит в том, чтобы рекурсивно пройтись по всем текстовым нодам и внедрить туда смайлы.</p>
<p>Выглядит это так:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">blacklist</span> <span class="o">=</span> <span class="p">[</span>
<span class="nx">HTMLScriptElement</span><span class="p">,</span>
<span class="nx">HTMLTextAreaElement</span><span class="p">,</span>
<span class="nx">HTMLInputElement</span><span class="p">,</span>
<span class="nx">HTMLSelectElement</span><span class="p">,</span>
<span class="nx">HTMLButtonElement</span><span class="p">,</span>
<span class="p">];</span>
<span class="kd">const</span> <span class="nx">skip</span> <span class="o">=</span> <span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">cls</span> <span class="k">of</span> <span class="nx">blacklist</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">node</span> <span class="k">instanceof</span> <span class="nx">cls</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">recursiveEmojification</span> <span class="o">=</span> <span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeType</span> <span class="o">!==</span> <span class="nx">Node</span><span class="p">.</span><span class="nx">TEXT_NODE</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeType</span> <span class="o">===</span> <span class="nx">Node</span><span class="p">.</span><span class="nx">ELEMENT_NODE</span> <span class="o">&&</span> <span class="o">!</span><span class="nx">skip</span><span class="p">(</span><span class="nx">node</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">node</span><span class="p">.</span><span class="nx">childNodes</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">_</span> <span class="o">=></span> <span class="nx">recursiveEmojification</span><span class="p">(</span><span class="nx">_</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Do something with 'node.nodeValue'</span>
<span class="p">}</span>
<span class="nb">document</span>
<span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="dl">"</span><span class="s2">.js-emojify</span><span class="dl">"</span><span class="p">)</span>
<span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">_</span> <span class="o">=></span> <span class="nx">recursiveEmojification</span><span class="p">(</span><span class="nx">_</span><span class="p">));</span>
</code></pre></div></div>
<p>В общем, ничего сложного. Обратите особое внимание на функцию <code class="language-plaintext highlighter-rouge">skip</code>.
Именно она гарантирует “невредимость” некоторых элементов страницы.
А именно теги <code class="language-plaintext highlighter-rouge">script</code>, <code class="language-plaintext highlighter-rouge">textarea</code> и подобные им.</p>
<h3 id="внедрение-emoji-в-текст">Внедрение Emoji в текст</h3>
<p>Осталось дело за малым. Или нет? :thinking_face:</p>
<p>Теперь у нас есть все куски текстов, в которых потенциально могут быть emoji.
Если нам нужна совместимость только с девайсами Apple, то мы можем заменить все <code class="language-plaintext highlighter-rouge">:emoji:</code> смайлы
на обычный unicode и на этом успокоиться. Но у меня Линукс. И многих других тоже нет ничего яблочного.
Поэтому нужно заменить все текстовые смайлы, на маленькие картинки. Вот тут и начинается самое интересное.</p>
<p>Дело в том, что мы не можем просто взять и, вместо текстовой ноды, вкорячить кусок html кода.
Если попробовать сделать <code class="language-plaintext highlighter-rouge">node.nodeValue = '<span>Hi</span>'</code>, то на выходе будет экранированный html код.</p>
<p>Поэтому этот код не будет работать, как этого хочется:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">emojified</span> <span class="o">=</span> <span class="nx">emojificator</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeValue</span><span class="p">);</span>
<span class="c1">// Пропускаем дальнейшие манипуляции, если ничего не изменилось</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">emojified</span> <span class="o">===</span> <span class="nx">node</span><span class="p">.</span><span class="nx">nodeValue</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">node</span><span class="p">.</span><span class="nx">nodeValue</span> <span class="o">=</span> <span class="nx">emojified</span><span class="p">;</span>
</code></pre></div></div>
<p>А вот результат:</p>
<figure class="frame "><a href="./broken-emoji.png" target="_blank"><img src="./broken-emoji.png" alt="Кривой вывод Emoji" title="Кривой вывод Emoji" /></a></figure>
<p>Так себе… Не то, чего я хотел. Поэтому проделываем следующий финт:</p>
<ol>
<li>Создаем новый элемент <code class="language-plaintext highlighter-rouge">span</code>;</li>
<li>Наполняем его нужным нам html кодом;</li>
<li>Вставляем его в документ после нашего исходного текста;</li>
<li>Удаляем старый текст.</li>
</ol>
<p>Вот так это выглядит:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">replacement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">span</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// (1)</span>
<span class="nx">replacement</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">js-emojified</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// Для наглядности добавим класс</span>
<span class="nx">replacement</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">emojified</span><span class="p">;</span> <span class="c1">// (2)</span>
<span class="nx">node</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">insertBefore</span><span class="p">(</span><span class="nx">replacement</span><span class="p">,</span> <span class="nx">node</span><span class="p">);</span> <span class="c1">// (3)</span>
<span class="nx">node</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">removeChild</span><span class="p">(</span><span class="nx">node</span><span class="p">);</span> <span class="c1">// (4)</span>
</code></pre></div></div>
<p>Да, мы создали еще один элемент. Но это не должно быть проблемой, так как чистый <code class="language-plaintext highlighter-rouge">span</code> ни на что не влияет.</p>
<h2 id="результат">Результат</h2>
<p>На выходе получилось достаточно аккуратное решение. Хоть и с небольшими хаками.</p>
<details class="spoiler"><summary class="spoiler-title">Соберем все воедино</summary><div class="spoiler-body">
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">blacklist</span> <span class="o">=</span> <span class="p">[</span>
<span class="nx">HTMLScriptElement</span><span class="p">,</span>
<span class="nx">HTMLTextAreaElement</span><span class="p">,</span>
<span class="nx">HTMLInputElement</span><span class="p">,</span>
<span class="nx">HTMLSelectElement</span><span class="p">,</span>
<span class="nx">HTMLButtonElement</span><span class="p">,</span>
<span class="p">];</span>
<span class="kd">const</span> <span class="nx">skip</span> <span class="o">=</span> <span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">cls</span> <span class="k">of</span> <span class="nx">blacklist</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">node</span> <span class="k">instanceof</span> <span class="nx">cls</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">recursiveEmojification</span> <span class="o">=</span> <span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeType</span> <span class="o">!==</span> <span class="nx">Node</span><span class="p">.</span><span class="nx">TEXT_NODE</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeType</span> <span class="o">===</span> <span class="nx">Node</span><span class="p">.</span><span class="nx">ELEMENT_NODE</span> <span class="o">&&</span> <span class="o">!</span><span class="nx">skip</span><span class="p">(</span><span class="nx">node</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">node</span><span class="p">.</span><span class="nx">childNodes</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">_</span> <span class="o">=></span> <span class="nx">recursiveEmojification</span><span class="p">(</span><span class="nx">_</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">emojified</span> <span class="o">=</span> <span class="nx">emojificator</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeValue</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">emojified</span> <span class="o">===</span> <span class="nx">node</span><span class="p">.</span><span class="nx">nodeValue</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nx">replacement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">span</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">replacement</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">js-emojified</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">replacement</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">emojified</span><span class="p">;</span>
<span class="nx">node</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">insertBefore</span><span class="p">(</span><span class="nx">replacement</span><span class="p">,</span> <span class="nx">node</span><span class="p">);</span>
<span class="nx">node</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">removeChild</span><span class="p">(</span><span class="nx">node</span><span class="p">);</span>
<span class="p">}</span>
<span class="nb">document</span>
<span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="dl">"</span><span class="s2">.js-emojify</span><span class="dl">"</span><span class="p">)</span>
<span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">_</span> <span class="o">=></span> <span class="nx">recursiveEmojification</span><span class="p">(</span><span class="nx">_</span><span class="p">));</span>
</code></pre></div></div>
</div></details>
<p>Не могу сказать, что смайлы для меня настолько важны, чтобы я стал так заморачиваться.
Скорее то, что это был определенный вызов, интересная задача на исследование.
Я узнал много нового и стал лучше понимать работу html и js в браузере.
И сделал свой сайт чуточку лучше :blush:</p>
<p>Спасибо за внимание. Надеюсь, было интересно :fox:</p>]]></content><author><name>Vitaly Zaslavsky</name><email>vtvz@pm.me</email></author><category term="Разработка" /><category term="Веб-разработка" /><category term="Frontend" /><category term="Emoji" /><summary type="html"><![CDATA[Простой и надежный способ внедрения Emoji в текст без влияния на другие компоненты страницы]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vtvz.me/blog/reliable-emojification/cover.png" /><media:content medium="image" url="https://vtvz.me/blog/reliable-emojification/cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="ru"><title type="html">Покорение UTM меток</title><link href="https://vtvz.me/blog/conquering-the-utm-parameters/" rel="alternate" type="text/html" title="Покорение UTM меток" /><published>2020-05-13T00:00:00+03:00</published><updated>2020-05-13T00:00:00+03:00</updated><id>https://vtvz.me/blog/conquering-the-utm-parameters</id><content type="html" xml:base="https://vtvz.me/blog/conquering-the-utm-parameters/"><![CDATA[<p>Решил я тут собрать побольше статистики по посетителям.
Хотя Яндекс.Метрика очень даже неплохо с этим справляется,
но хотелось бы иметь более управляемый механизм,
в котором можно было разделить не только соц. сеть,
из которой пришел посетитель, но и какую из ссылок он нажал.</p>
<p>Или например, сколько человек посмотрело статью из секции “Latest Article” на главной странице,
сколько перелистнуло на следующий пост, и так далее. Выбор очевиден – нужно использовать UTM метки.
Они очень распространены в рекламной части бизнеса, но их можно использовать и для простой аналитики.
Но тут есть одна проблемка…</p>
<!--cut-->
<h2 id="постановка-задачи">Постановка задачи</h2>
<p>Проблема в том, что эти метки <strong>очень</strong> массивные.
Вот к примеру, одна из возможных ссылок на пост в социальной сети выглядит следующим образом:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://vtvz.me/blog/social-post-generator/
?utm_source=_telegram
&utm_medium=social-post
&utm_campaign=blog
&utm_content=social-post-generator
</code></pre></div></div>
<p>Это 96 дополнительных символов!
Хотя многие соц. сети хорошо с этим справляются. Например, в VK такие ссылки ограничены одной строкой.
А в Twitter и LinkedIn есть свой укорачиватель. Но вот Телеграм так не умеет.</p>
<figure class="frame "><a href="./telegram.png" target="_blank"><img src="./telegram.png" alt="Жирная ссылка в Телеграмм" title="Жирная ссылка в Телеграмм" /></a></figure>
<p>Тем не менее, помимо постов есть еще и ссылки на сайт в профилях соц. сетей.
В ВК, например, у меня целых 3 места, ведущих на главную страницу.
И эти длиннющие ссылки немного пугают и уродливо выглядят.
Короче, задача в следующем: сделать эти ссылки простыми и эстетичными.</p>
<h2 id="решения">Решения</h2>
<h3 id="укорачиватель-ссылок">Укорачиватель ссылок</h3>
<p>Я уже давно пользуюсь сервисами, которые делают из монстров конфетку.
Вот например <a href="https://bitly.com/">Bitly</a> неплохо с этим справляется.
Вот что он сделал со ссылкой, которую я указал ранее:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://bit.ly/vtvz-tg-post-generator
</code></pre></div></div>
<p>Уже гораздо лучше.</p>
<p>НО! Теперь эта ссылка не ведет на мой сайт. Она ведет на bit.ly, который уже кидает куда надо.
Проблема ли это? Для меня да. Мой сайт – моя визитная карточка, которая должна явно указывать, кому она принадлежит.
Так что, это отметается сразу.</p>
<h3 id="свой-укорачиватель-ссылок">Свой укорачиватель ссылок</h3>
<p>А почему бы самому не укорачивать ссылки и публиковать уже их? Это решает проблему выше, но создает еще несколько.</p>
<ol>
<li>Его нужно сделать. Это дополнительное время на реализацию целого компонента системы.
Сложность создает тот факт, что этот сайт статический, собран из обычных html страничек,
сгенерированных с помощью Jekyll, который написан на Ruby, который я не знаю… Ну вы поняли :grin:</li>
<li>Обычные укорачиватели перекидывают тебя на другой сайт используя HTTP 301 код.
Но мы не можем отдавать этот статус клиенту, потому что статические сайты дают либо 200, либо 404.
А вот ВК не умеет обрабатывать такие страницы.</li>
<li>Придется генерировать целую пачку укороченных сслылок для каждой записи в блоге.
Это значительно повышает ошибку человеческого фактора, значительно усложняет систему и захламляет проект.
<span class="text-muted">(Эта проблема справедлива и для предыдущего метода)</span></li>
<li>Это сложно автоматизировать имеющимися инструментами.</li>
</ol>
<p>Вывод: тоже отбрасываем.</p>
<h3 id="упаковка-меток">Упаковка меток</h3>
<p>Я уверен, что этот метод использовался ранее, и что я изобрел велосипед.
Но результат, по-моему, получился просто невероятным. Принцип работы в следующем:</p>
<p>Вместе со ссылкой передается один единственный query параметр,
на основании его в url страницы применяются все необходимые utm метки.
Все это должно произойти <strong>ДО</strong> инициализации инструментов аналитики.</p>
<p>Выглядит это следующим образом:</p>
<ul>
<li>Человек открывает ссылку <code class="language-plaintext highlighter-rouge">https://vtvz.me/blog/social-post-generator/?utm=tg</code></li>
<li>Она распаковывается в
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://vtvz.me/blog/social-post-generator/
?utm_source=_telegram
&utm_medium=social-post
&utm_campaign=blog
&utm_content=social-post-generator
</code></pre></div> </div>
</li>
<li>Запускаются Яндекс.Метрика и Google Analytics</li>
</ul>
<p>В итоге удалось упаковать 96 символов в 7! Вот как это работает…</p>
<h2 id="реализация">Реализация</h2>
<div class="alert alert-info">
<p><b>Предупреждение!</b></p>
<p>
Весь дальнейший код написан на версии JavaScript, который поддерживается не всеми браузерами.
Для совместимости со старыми версиями я использую webpack + babel.
Но примеры достаточно просты, чтобы транспилировать их в классический JS.
</p>
</div>
<p>Наш код должен работать следующим образом:</p>
<ol>
<li>Вытянуть <code class="language-plaintext highlighter-rouge">utm</code> параметр из query адреса;</li>
<li>Сгенерировать source, medium, campaign…</li>
<li>Удалить параметр <code class="language-plaintext highlighter-rouge">utm</code>;</li>
<li>Применить метки из пункта 2;</li>
<li>Заменить текущий url на новый.</li>
</ol>
<p>Выглядит это так:</p>
<p>У нас есть карта стратегий обработки. Это объект, в ключах которого возможные utm сокращения,
а в значении – функция, которая генерирует все необходимые метки.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">strategies</span> <span class="o">=</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">tg</span><span class="dl">'</span><span class="p">:</span> <span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">collection</span><span class="p">,</span> <span class="nx">slug</span><span class="p">]</span> <span class="o">=</span> <span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">).</span><span class="nx">filter</span><span class="p">(</span><span class="nx">_</span> <span class="o">=></span> <span class="nx">_</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">source</span><span class="p">:</span> <span class="dl">'</span><span class="s1">_telegram</span><span class="dl">'</span><span class="p">,</span>
<span class="na">medium</span><span class="p">:</span> <span class="dl">'</span><span class="s1">social-post</span><span class="dl">'</span>
<span class="p">}</span>
<span class="nx">collection</span> <span class="o">&&</span> <span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">campaign</span> <span class="o">=</span> <span class="nx">collection</span><span class="p">);</span>
<span class="nx">slug</span> <span class="o">&&</span> <span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">content</span> <span class="o">=</span> <span class="nx">slug</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">data</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>
<p>В этом случае функция получает текущий адрес, на основании которого строит кампанию и контент.
Эта магия применима в моем случае. В вашем может быть что-то другое.</p>
<figure class="frame "><a href="./strategies.png" target="_blank"><img src="./strategies.png" alt="Карта стратегий" title="Карта стратегий" /></a><figcaption><p>У меня карта стратегий выглядит вот так</p></figcaption></figure>
<p>Теперь нужна функция, которая сделает всю остальную работу:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* @param {Window} w
* @param {Document} d
*/</span>
<span class="kd">const</span> <span class="nx">utmResolver</span> <span class="o">=</span> <span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="nx">d</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="c1">// В качестве параметров передаются window и document</span>
<span class="c1">// 1. Парсим ссылку и вытягиваем utm параметр </span>
<span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">toString</span><span class="p">());</span>
<span class="kd">const</span> <span class="nx">utm</span> <span class="o">=</span> <span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">utm</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="kc">null</span> <span class="o">===</span> <span class="nx">utm</span> <span class="o">||</span> <span class="dl">''</span> <span class="o">===</span> <span class="nx">utm</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// В случае отсутствия стратегии просто ничего не делаем</span>
<span class="kd">let</span> <span class="nx">strategy</span> <span class="o">=</span> <span class="nx">strategies</span><span class="p">[</span><span class="nx">utm</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="kc">undefined</span> <span class="o">===</span> <span class="nx">strategy</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// 2. Генерируем значения меток </span>
<span class="kd">const</span> <span class="p">{</span>
<span class="nx">source</span><span class="p">,</span>
<span class="nx">medium</span><span class="p">,</span>
<span class="nx">campaign</span><span class="p">,</span>
<span class="nx">content</span><span class="p">,</span>
<span class="p">}</span> <span class="o">=</span> <span class="nx">strategy</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
<span class="c1">// 3. Удаляем utm </span>
<span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="dl">'</span><span class="s1">utm</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// 4. Применяем параметры </span>
<span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="dl">'</span><span class="s1">utm_source</span><span class="dl">'</span><span class="p">,</span> <span class="nx">source</span><span class="p">);</span>
<span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="dl">'</span><span class="s1">utm_medium</span><span class="dl">'</span><span class="p">,</span> <span class="nx">medium</span><span class="p">);</span>
<span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="dl">'</span><span class="s1">utm_campaign</span><span class="dl">'</span><span class="p">,</span> <span class="nx">campaign</span><span class="p">);</span>
<span class="nx">content</span> <span class="o">&&</span> <span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="dl">'</span><span class="s1">utm_content</span><span class="dl">'</span><span class="p">,</span> <span class="nx">content</span><span class="p">);</span>
<span class="c1">// 5. Обновляем URL</span>
<span class="nx">w</span><span class="p">.</span><span class="nx">history</span>
<span class="p">?</span> <span class="nx">w</span><span class="p">.</span><span class="nx">history</span><span class="p">.</span><span class="nx">replaceState</span><span class="p">({},</span> <span class="nx">d</span><span class="p">.</span><span class="nx">title</span><span class="p">,</span> <span class="nx">url</span><span class="p">.</span><span class="nx">toString</span><span class="p">())</span>
<span class="p">:</span> <span class="nx">w</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">toString</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Нам остается только вызвать эту функцию:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">utmResolver</span><span class="p">(</span><span class="nb">window</span><span class="p">,</span> <span class="nb">document</span><span class="p">);</span>
</code></pre></div></div>
<div class="alert alert-info">
<p><b>Напоминание!</b></p>
<p>
Напомню, что это должно произойти ДО инициализации сборщиков статистики в синхронном потоке!
Нарушение этих правил может привести и приведет к искажению результатов.
</p>
</div>
<p>Благодаря этому ссылки стали куда лаконичнее, чем изначально.
Выбранный мною способ не является единственно верным.
Есть несколько альтернатив, как передать стратегию:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">vtvz.me/.../?u=tg</code></li>
<li><code class="language-plaintext highlighter-rouge">vtvz.me/.../?_=tg</code></li>
<li><code class="language-plaintext highlighter-rouge">vtvz.me/.../?=tg</code></li>
<li><code class="language-plaintext highlighter-rouge">vtvz.me/.../?tg</code></li>
<li><code class="language-plaintext highlighter-rouge">vtvz.me/.../#tg</code></li>
</ul>
<p>Ничто не мешает вам модифицировать приведенный выше скрипт под свои нужды.</p>
<p>Мне не удалось протестировать это дело в больших масштабах,
но текущие данные показывают правдивые результаты.
Так или иначе, если что-то пойдет не так – я сообщу.</p>
<p>А пока, спасибо за внимание :grin:
Если возникают вопросы, не стесняйтесь задавать их мне по любому доступному каналу,
пока комментарии не появились :fox:</p>]]></content><author><name>Vitaly Zaslavsky</name><email>vtvz@pm.me</email></author><category term="Разработка" /><category term="Веб-разработка" /><category term="Аналитика" /><category term="Frontend" /><summary type="html"><![CDATA[Элегантный способ укоротить UTM метки]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vtvz.me/blog/conquering-the-utm-parameters/cover.jpg" /><media:content medium="image" url="https://vtvz.me/blog/conquering-the-utm-parameters/cover.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="ru"><title type="html">Генератор постов в социальные сети</title><link href="https://vtvz.me/blog/social-post-generator/" rel="alternate" type="text/html" title="Генератор постов в социальные сети" /><published>2020-05-04T00:00:00+03:00</published><updated>2020-05-04T00:00:00+03:00</updated><id>https://vtvz.me/blog/social-post-generator</id><content type="html" xml:base="https://vtvz.me/blog/social-post-generator/"><![CDATA[<p>Порой написание статьи отнимает меньше времени, чем генерация постов в социальные сети.
И в каждой из них нужно что-то учесть:
в twitter’е жесткие ограничения по количеству символов,
где-то не нужно указывать ссылку на телеграмм канал,
а вот на личной странице в ВК еще нужно указать линку на группу.
Мне это поднадоело. Поэтому я решил это дело автоматизировать! Хватит это терпеть!</p>
<!--cut-->
<p>Это второй по счету интерактивный пост, призванный упростить мне жизнь.
Но в этом случае этот инструмент не будет полезен никому, кроме как мне.
И здесь сделано все так, чтобы создание постов для социальных сетей было тривиальным действием.
Если вдруг кто-то хочет себе что-то подобное – обращайтесь :blush:</p>
<p>Фичи этого генератора:</p>
<ul>
<li>Все поля предзаполняются автоматически из данных последнего поста блога;</li>
<li>Если нужно создать пост для какой-то другой коллекции, то для этого есть выпадающий список “Применить”;</li>
<li>Текст для twitter’a и других соц. сетей заполняется отдельно;</li>
<li>Имеется подсчет доступных символов для twitter’а и LinkedIn;</li>
<li>Для twitter’а подсчет символов выполнен с учетом всей магии с укорачиванием ссылок –
длинна урла не влияет на количество свободных буков;</li>
<li>Для каждой социальной сети используется свой шаблон поста;</li>
<li>В ссылку к каждой соц. медиа вставляются все необходимые utm метки для аналитики.</li>
</ul>
<p>В целом, получилось достаточно мощно. Это значительно упростит мне жизнь.</p>
<p>Если интересно, то вот, можно потыкать:</p>
<div id="social-post-generator-container">
<div class="progress">
<div class="progress-bar progress-bar-striped active" style="width: 100%"></div>
</div>
</div>]]></content><author><name>Vitaly Zaslavsky</name><email>vtvz@pm.me</email></author><category term="Разработка" /><category term="Веб-разработка" /><category term="Инструменты" /><summary type="html"><![CDATA[Генератор постов в социальные сети с учетом максимальной длины и применением всех необходимых utm меток]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vtvz.me/blog/social-post-generator/cover.jpg" /><media:content medium="image" url="https://vtvz.me/blog/social-post-generator/cover.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="ru"><title type="html">Покойся с миром, мой дорогой друг…</title><link href="https://vtvz.me/blog/rest-in-peace-my-friend/" rel="alternate" type="text/html" title="Покойся с миром, мой дорогой друг…" /><published>2020-04-24T00:00:00+03:00</published><updated>2020-04-24T00:00:00+03:00</updated><id>https://vtvz.me/blog/rest-in-peace-my-friend</id><content type="html" xml:base="https://vtvz.me/blog/rest-in-peace-my-friend/"><![CDATA[<p>В конце сентября 2017 года наша семья стала чуточку больше.
Мы пришли к выводу, что в нашем доме недостаточно уютно, и было принято решение взять хомячка.
Еще до того, как он у нас появился, ему было дано имя – Джонатан Хамстер.
Ведь какое еще можно дать имя хомяку?
Он быстро стал частью нашей семьи и поселился в нашем доме и наших сердцах.</p>
<!--cut-->
<figure class="frame post-image post-image--left"><a href="./sitting.jpg" target="_blank"><img src="./sitting.jpg" alt="" title="" /></a></figure>
<p>С самого начала он был дружелюбен. Было пару раз, что он кусался. Но это было давно и неправда.
В целом, его можно было взять на руки в любое время. Иногда мы его пугали –
не всегда он был готов к контакту с нами. Но, несмотря на это, он не кусался и не царапался.</p>
<p>Он был дружелюбен не только к нам.
Возможно, это общее свойство хомяков – доверять либо никому, либо всем, –
но не было ни одного случая агрессии по отношению к другим людям и животным.
Он чувствовал себя спокойно на руках любого.
Мы пытались подружить его и Джейсона (кота), но он все таки мышь, а кот – охотник. Так что с ними не заладилось.
Возможно, спустя какое-то время можно было попробовать еще раз, но мы рисковать не стали.</p>
<figure class="frame post-image post-image--right"><a href="./running.jpg" target="_blank"><img src="./running.jpg" alt="" title="" /></a></figure>
<p>Он был активным и любопытным. Его невозможно было сфотографировать.
По ночам он бегал по колесу, чем-то шуршал в своем домике, носился по этажам своей уютной клетки.
Чтобы было теплее, мы давали ему кусочки мягкой туалетной бумаги,
которые он с удовольствием брал и упихивал в свое жилище.
Интересно то, что он всегда знал, что с ней делать.
Стоило ему дать бумажку, он сразу утолкает ее за щеку и потащит в свою конуру.</p>
<p>Со временем он стал реагировать на наш голос.
Иногда, когда мы его звали, он высовывал свой носик из будки и выходил к нам, чтобы его взяли на ручки.
Бывало, он сам просился – забирался на клетку и просовывал свой носик между прутьями,
крепко держась лапками за них.</p>
<figure class="frame post-image post-image--left"><a href="./pocket.jpg" target="_blank"><img src="./pocket.jpg" alt="" title="" /></a></figure>
<p>Иногда, когда он был в руках, мы давали ему какую-то вкусняшку, типа яблока.
Чаще всего он ухомячивал её за щеку. Но иногда он кушал прямо на руках, что выглядело очень мило.
Бывало, что я усаживал его в кармашек на своей футболке. Один раз он там уснул.
Был случай, когда он сначала набил щеки зернами в клетке, я положил в карман, а он выложил всю свою еду прям там.</p>
<p>Он прожил хорошую, счастливую хомячью жизнь, в тепле, любви и уюте.
У него всегда была еда, вода и чистота в клетке.
Мы никогда не давали ему то, что могло навредить,
но старались всегда делиться с ним фруктами и овощами, которые можно.
Он никогда не падал – мы были с ним аккуратны.</p>
<p>Но хомяки живут недолго. Чаще всего они умирают в возрасте 1-2 года.
Наш оказался долгожителем – 2 года и 9 месяцев. Но и его время уже прошло.
Под старость лет своих он стал немного странно вести себя.
Он старался ухомячить любую ткань, с которой соприкасался, чего раньше не было.
Также он перестал спать в домике – там оставалась только голова, а все остальное было снаружи.
Он и до этого был серым, но поседел еще больше.</p>
<p>22 апреля 2020 года мы обнаружили его в своей клетке, лежащим на боку у выхода из домика мордочкой наружу.
Мы подумали, что он мертв, но он продолжал дышать.
У него случались такие-то приступы, когда он начинал сильно дергать передними лапами, вытягивая их вперед.
Его рот двигался произвольно, иногда открывался, а иногда он как-будто что-то жевал.
Ему определенно было плохо. Он умирал буквально у нас на руках.
Мы думали, что он уйдет сам или ему станет лучше.</p>
<figure class="frame post-image post-image--right"><a href="death.jpg" target="_blank"><img src="death.jpg" alt="" title="" /></a></figure>
<p>Но был вечер. А потом настало утро. Но он все был жив, но совершенно нежизнеспособен.
Ему все еще было плохо. Он ничего не ел и не пил. Поэтому было решено отвезти к ветеринару.
Возможно, благодаря карантину нам удалось пробиться к Бокаревым, где нас смогли принять буквально сразу.
Его состояние уже было неисправимо. Если я правильно понял врача, у него было кровоизлияние в мозг.
Но сердце его было крепким, благодаря чему он и жил так долго.
На вопрос, было ли ему плохо, чувствовал ли он боль. Она сказала, что он был в коматозе и ничего не чувствовал.
Если бы я был ветеринаром, то говорил бы то же самое. Но мне проще верить, что это действительно так.
В итоге, его пришлось усыпить. Он ушел 11:10 23 апреля 2020 года.</p>
<figure class="frame post-image post-image--left"><a href="./sitting.jpg" target="_blank"><img src="./sitting.jpg" alt="" title="" /></a></figure>
<p>Я хотел, чтобы он умер при нас, чтобы была возможность с ним попрощаться.
И мы были рядом, когда ему стало плохо, и когда его не стало…</p>
<p>Похоронили мы его в этот же день вечером.
Мы выкопали ему ямку, насыпали древесный наполнитель,
поставили его деревянный домик, в котором он жил, сверху и положили его туда.
Я хотел отдать ему должное за ту радость и счастье, которые он нам подарил,
и похоронить его достойным образом.</p>
<p>Несмотря на то, что я знал, что он скоро умрет, я все равно не был к этому готов.
Мне очень сложно принять его смерть и то, что его больше нет.
Я разбит и расстроен. В доме стало немного пусто, и мне нужно к этому привыкнуть.</p>
<p>Мы любили его. Я любил его. А он любил нас. Покойся с миром, мой дорогой друг…</p>]]></content><author><name>Vitaly Zaslavsky</name><email>vtvz@pm.me</email></author><category term="LiveLog" /><category term="Блог" /><category term="Животные" /><category term="RIP" /><summary type="html"><![CDATA[В конце сентября 2017 года наша семья стала чуточку больше. Мы пришли к выводу, что в нашем доме недостаточно уютно, и было принято решение взять хомячка. Еще до того, как он у нас появился, ему было дано имя – Джонатан Хамстер. Ведь какое еще можно дать имя хомяку? Он быстро стал частью нашей семьи и поселился в нашем доме и наших сердцах.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vtvz.me/blog/rest-in-peace-my-friend/cover.jpg" /><media:content medium="image" url="https://vtvz.me/blog/rest-in-peace-my-friend/cover.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="ru"><title type="html">Ковыряясь под капотом #2</title><link href="https://vtvz.me/blog/under-the-hood-2nd/" rel="alternate" type="text/html" title="Ковыряясь под капотом #2" /><published>2020-04-18T00:00:00+03:00</published><updated>2020-04-18T00:00:00+03:00</updated><id>https://vtvz.me/blog/under-the-hood-2nd</id><content type="html" xml:base="https://vtvz.me/blog/under-the-hood-2nd/"><![CDATA[<p>В голове у меня много мыслей, которые хотелось бы выразить в виде статьи.
У меня даже есть небольшая заметка в Google Keep с темами потенциальных постов.
Но это сложно. Русский язык – сложный язык.
На написание одной статьи на 5 минут чтения может уйти несколько часов, а то и дней.
Стоит учесть еще тот факт, что блог не приносит мне ни копейки.
Поэтому, время на написание статьи есть только вечером после работы или на выходных.</p>
<p>Поэтому, я делаю то, что не заставляет меня слишком много думать и не требует полной сосредоточенности.
Я улучшаю творение рук моих. Удивительно то, что, несмотря на простую концепцию, всегда есть куда улучшаться.
Именно об этом будет этот пост.</p>
<!--cut-->
<h2 id="коллекции">Коллекции</h2>
<p>Начну с фундаментальных вещей. На сайте появился полноценный раздел, который я решил назвать “<a href="/collections/">Коллекциями</a>”.
Сначала были одни <a href="/achievements/"><i class="fa-solid fa-trophy"></i> достижения</a> – мои успехи в личной и профессиональной жизни.
И выглядел он очень даже неплохо. Каждое достижение – это отдельная карточка,
в которой собраны ссылки и материалы, использованные мной.</p>
<p>Ко всему этому туда были добавлены
<a href="/notes/"><i class="fa-solid fa-sticky-note"></i> заметки</a>
и <a href="/bookmarks/"><i class="fa-solid fa-bookmark"></i> закладки</a>.
Первые – для записи коротких мыслей на свободные темы.
Вторые – для сохранения ссылок на интересные и полезные материалы.
Опять же, чтобы не плодить десяток разделов с несколькими записями в каждом,
все эти коллекции были упакованы в один с общей лентой,
посты которых отмечены соответствующей иконкой.</p>
<p>В мыслях есть создать еще два раздела: программы и книги.
Думаю, названия говорят сами за себя. Мне есть чем поделиться.</p>
<p>Тем не менее, если хочется увидеть что-то одно, например заметки,
то их можно отфильтровать в столбце справа,
а на мобильных устройствах – в самом низу (я думаю над тем, как сделать это лучше).
Фильтр по категориям и тегам тоже скоро будет (наверное).</p>
<p>Особенно мне нравится градиент в самом верху карточек.
Искать картинку для каждой записи мне не хотелось.
Но оставлять пост без какого-либо цвета очень скучно и некрасиво.
На текущий момент доступно 22 градиента(ов),
которые выбираются псевдослучайно (в зависимости от количества символов в записи)</p>
<p>У вас тоже есть возможность поучаствовать в улучшении моего сайта:</p>
<ol>
<li>Если у вас есть любимые красивые цветовые градиенты, отправьте их мне любым <a href="/">доступным способом</a>.</li>
<li>Также, вы можете <a href="https://fontawesome.com/icons?d=gallery&s=solid&m=free">выбрать красивую иконку</a> для раздела коллекций.
Я пока выбрал звездочку <i class="fa-solid fa-star fa-fw"></i>, но, возможно, у вас будет идея получше :wink:</li>
</ol>
<h2 id="иконка">Иконка</h2>
<p>Очередное глобальное изменение.
Это, конечно, не так впечатляет, как новый раздел с коллекциями.
Но этот маленький аттрибут очень много значит для любого сайта. По иконке встречают, как говорится.
Старый значок служил мне верой и правдой долгих 5 лет, если не больше,
и он останется в моём сердце и истории git навсегда :heart:</p>
<p>Конечно, я выбрал лису – у меня фон с лисой и логотип с лисой. Люблю я лис :fox:
Пока что я взял Emoji от Apple, сделал ее черно-белой и немного добавил яркости.
Надеюсь меня не засудят за нарушение авторских прав :no_mouth:
Возможно, я нарисую что-то более уникальное. Но я не художник и не дизайнер.
Если хочется помочь мне в это деле – пожалуйста. Мне будет очень приятно :blush:</p>
<h2 id="мелочи">Мелочи</h2>
<h3 id="теги-и-категории">Теги и Категории</h3>
<p>На страницах блога в правой колонке есть два блока: комментарии и теги.</p>
<p>Некоторые считают, что это лишнее и создаёт информационный шум.
Частично я с этим согласен. Но люди достаточно часто переходят по этим ссылкам.</p>
<p>Чтобы это выглядело более презентабельно, я внес пару косметических улучшений:</p>
<ol>
<li>Теперь пункты отсортированы по количеству записей. В прошлом они были разбросаны хаотично.</li>
<li>Блок тегов теперь отображает только 6 самых популярных. Остальные скрыты за кнопочной “Show N more…”</li>
<li>Благодаря <a href="https://www.w3schools.com/css/css3_flexbox.asp">flex-box</a> удалось сделать кнопочки тегов на всю ширину.
Теперь это выглядит как солидный блок, а не небрежно напиханные элементы.</li>
</ol>
<table class="table table-borderless">
<capture>Результат (до / после)</capture>
<tr>
<td><figure class="frame "><a href="tags-before.png" target="_blank"><img src="tags-before.png" alt="До" title="До" /></a></figure></td>
<td><figure class="frame "><a href="tags-after.png" target="_blank"><img src="tags-after.png" alt="После" title="После" /></a></figure></td>
</tr>
</table>
<h3 id="звездочки">Звездочки</h3>
<p>Это очень незначительное изменение, но в разы улучшает моё резюме, особенно при печати.
Я прижал звездочки в скиллах вправо, что сделало табличку в разы лучше.</p>
<table class="table table-borderless">
<capture>Скриншоты "до" и "после" скажут все за меня:</capture>
<tr>
<td><figure class="frame "><a href="stars-before.png" target="_blank"><img src="stars-before.png" alt="До" title="До" /></a></figure></td>
<td><figure class="frame "><a href="stars-after.png" target="_blank"><img src="stars-after.png" alt="После" title="После" /></a></figure></td>
</tr>
</table>
<h3 id="прочее">Прочее</h3>
<p>Несколько других мелочей:</p>
<ol>
<li>Теперь у подзаголовков в статьях есть значок #, нажав на который можно получить ссылку на этот раздел статьи.
Это будет удобно, когда нужно сослаться на какую-то часть публикации в других статьях или при отправке людям.
Откровенно говоря, я сделал это просто потому что хочу, а не потому что надо :blush:</li>
<li>Подсчет времени на прочтение статьи стал еще более правдоподобным благодаря плагину, который учитывает только текст,
а не весь код. Ранее, большие куски кода могли добавлять больше минут на прочтение, тогда как самого текста не так и много.</li>
<li>Теперь все картинки кликабельны и открывают изображение в новой вкладке.
Следующим шагом будет прикрутить плагин, который открывает картинку в текущем окне. Но это потом.</li>
</ol>
<hr />
<p>Если у вас есть какие-то идеи, напишите мне. Мне важна любая обратная связь.
Также можете предложить темы статей, которые хотите, чтобы я осветил.</p>
<p>Спасибо за внимание :fox:</p>]]></content><author><name>Vitaly Zaslavsky</name><email>vtvz@pm.me</email></author><category term="Разработка" /><category term="Блог" /><category term="Веб-разработка" /><category term="Jekyll" /><category term="Обновления" /><summary type="html"><![CDATA[Отчет о проделанной над сайтом работе на последние пару недель]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vtvz.me/blog/under-the-hood-2nd/cover.jpg" /><media:content medium="image" url="https://vtvz.me/blog/under-the-hood-2nd/cover.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="ru"><title type="html">Настройка robots.txt в Kubernetes</title><link href="https://vtvz.me/blog/robots-txt-in-kubernetes/" rel="alternate" type="text/html" title="Настройка robots.txt в Kubernetes" /><published>2020-03-21T00:00:00+03:00</published><updated>2020-03-21T00:00:00+03:00</updated><id>https://vtvz.me/blog/robots-txt-in-kubernetes</id><content type="html" xml:base="https://vtvz.me/blog/robots-txt-in-kubernetes/"><![CDATA[<p>Представьте себе, что на ваших плечах лежит инфраструктура около дюжины проектов,
которые написаны на разных языках программирования и имеют свой стек технологий.
Некоторые из них совсем свежие и используют самые современные веяния веб-индустрии,
а другие – кладезь легаси и устаревшего кода.</p>
<p>У каждого из этих проектов, помимо продового, есть еще 1-3 окружения для тестирования.
И вот вам, как DevOps инженеру прилетает следующая задача:
нужно для каждого тестового окружения добавить robots.txt файл,
который будет запрещать поисковикам индексацию.</p>
<p>Есть несколько способов, как это сделать, но я нашел самый простой и оптимальный.</p>
<!--cut-->
<p>Конечно, можно заставить всех бекендеров добавить этот файл в каждый проект,
но это сложный подход, который DevOps инженер не контролирует.
Поэтому мы пойдем по другому пути.</p>
<p>Решение очень зависит от используемых вами технологий.
У нас используются следущие:</p>
<ul>
<li><a href="https://kubernetes.io/">Kubernetes</a> кластер;</li>
<li><a href="https://kubernetes.github.io/ingress-nginx/">Ingress Nginx</a>;</li>
<li><a href="https://helm.sh/">Helm</a>.</li>
</ul>
<p>Каждый из этих инструментов вносит свои улучшения в решение этой задачи.</p>
<h2 id="чистый-kubernetes">Чистый Kubernetes</h2>
<p>Если у вас используется чистый Kubernetes без установленного Nginx Ingress, то у вас есть 2 пути.</p>
<h3 id="nginx">Nginx</h3>
<p>Вы можете взять самый легкий чистый образ с Nginx,
куда с помощью ConfigMap примонтировать как конфиг сервера,
так и содержимое файла <code class="language-plaintext highlighter-rouge">robots.txt</code>.
После этого достаточно только настроить Ingress на то, чтобы он все запросы на <code class="language-plaintext highlighter-rouge">/robots.txt</code> направлял в этот контейнер.</p>
<p>Примеры конфигов приводить не буду, так как есть вариант получше.</p>
<h3 id="robots-disallow">“Robots Disallow”</h3>
<p>Второе, еще более простое решение, это взять <a href="https://hub.docker.com/r/wikiwi/robots-disallow">wikiwi/robots-disallow</a>
образ в Docker Hub. Принцип работы абсолютно такой же, только теперь не придется ничего настраивать. Только Ingress.</p>
<h2 id="ingress-nginx">Ingress Nginx</h2>
<p>Если вы еще не установили его себе в кластер, то я вам <strong>настоятельно</strong> рекомендую это сделать.
Это мощный инструмент, который умеет делать все, что умеет Nginx,
но имеет такой же подход к использованию, как и все сущности в Kubernetes.</p>
<p>Вы только посмотрите на количество параметров,
которые можно настроить через <a href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/">аннотации</a> или <a href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/">конфиг мапы</a>.</p>
<p>В общем, этим мы и воспользуемся, чтобы решить поставленную задачу.
Достаточно добавить в Ingress приложения следующие строки:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">metadata</span><span class="pi">:</span>
<span class="na">annotations</span><span class="pi">:</span>
<span class="na">kubernetes.io/ingress.class</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">nginx.ingress.kubernetes.io/server-snippet</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">location = /robots.txt {</span>
<span class="s">add_header Content-Type text/plain;</span>
<span class="s">return 200 'User-agent: *\nDisallow: /\n';</span>
<span class="s">}</span>
</code></pre></div></div>
<p>Да, Nginx умеет не только выдавать статику или проксировать запросы. Он может генерировать ответ самостоятельно.
Строка <code class="language-plaintext highlighter-rouge">return 200 'Content'</code> именно это и делает.</p>
<p>Таким образом, мы решили задачу буквально в <strong>5 строк</strong>.</p>
<h2 id="helm">Helm</h2>
<p>В этой ситуации Helm не делает ничего особенного, но позволяет переиспользовать один манифест в разных окружениях.
В простейшем его использовании, Helm по своей сути является шаблонизатором для конфигов Kubernetes.</p>
<p>Поэтому можно сделать так:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">{{</span><span class="nv">- if ne .Values.env "production"</span> <span class="pi">}}</span>
<span class="na">nginx.ingress.kubernetes.io/server-snippet</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">location = /robots.txt {</span>
<span class="s">add_header Content-Type text/plain;</span>
<span class="s">return 200 'User-agent: *\nDisallow: /\n';</span>
<span class="s">}</span>
<span class="pi">{{</span><span class="nv">- end</span> <span class="pi">}}</span>
</code></pre></div></div>
<p>То есть если окружение не продовое, запрещаем поисковикам индексирование.</p>
<p>Либо так, если хочется большей власти:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">{{</span><span class="nv">- if .Values.disallowRobots</span> <span class="pi">}}</span>
<span class="na">nginx.ingress.kubernetes.io/server-snippet</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">location = /robots.txt {</span>
<span class="s">add_header Content-Type text/plain;</span>
<span class="s">return 200 'User-agent: *\nDisallow: /\n';</span>