diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c8bf9e..fc1ea34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,11 @@
 
 ## Canary
 
+## 1.24.0
+* Added favorites to eicon picker
 * Cleaned up top menu
+  * Post Ads and Ad Editor have been merged together into My Ads
   * Profile Helper now only shows up if you have anything to fix; otherwise the profile helper can be found in the Settings menu
-  * Post Ads and Ad Editor have been merged together
 
 ## 1.23.0
 * Improved text editor
diff --git a/bbcode/EIconSelector.vue b/bbcode/EIconSelector.vue
index ef353f9..94d06d8 100644
--- a/bbcode/EIconSelector.vue
+++ b/bbcode/EIconSelector.vue
@@ -1,73 +1,89 @@
 <template>
-  <div class="eicon-selector-ui">
-    <div v-if="!store || refreshing" class="d-flex align-items-center loading">
-      <strong>Loading...</strong>
-      <div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
-    </div>
-    <div v-else tabindex="0">
-      <div>
-        <div class="search-bar">
-          <input type="text" class="form-control search" id="search" v-model="search" ref="search" placeholder="Search eicons..." @click.prevent.stop="setFocus()" @mousedown.prevent.stop @mouseup.prevent.stop />
-          <div class="btn-group search-buttons">
-            <div class="btn expressions" @click.prevent.stop="runSearch('category:expressions')" aria-label="Expressions">
-              <i class="fas fa-theater-masks"></i>
-            </div>
+  <modal action="Select EIcon" ref="dialog" :buttons="false" dialogClass="eicon-selector big">
+    <div class="eicon-selector-ui">
+      <div v-if="!store || refreshing" class="d-flex align-items-center loading">
+        <strong>Loading...</strong>
+        <div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
+      </div>
+      <div v-else>
+        <div>
+          <div class="search-bar">
+            <input type="text" class="form-control search" id="search" v-model="search" ref="search" placeholder="Search eicons..." tabindex="0" @click.prevent.stop="setFocus()" @mousedown.prevent.stop @mouseup.prevent.stop />
+            <div class="btn-group search-buttons">
+              <div class="btn expressions" @click.prevent.stop="runSearch('category:favorites')" title="Favorites" role="button" tabindex="0">
+                <i class="fas fa-thumbtack"></i>
+              </div>
 
-            <div class="btn sexual" @click.prevent.stop="runSearch('category:sexual')" aria-label="Sexual">
-              <i class="fas fa-heart"></i>
-            </div>
+              <div class="btn expressions" @click.prevent.stop="runSearch('category:expressions')" title="Expressions" role="button" tabindex="0">
+                <i class="fas fa-theater-masks"></i>
+              </div>
 
-            <div class="btn bubbles" @click.prevent.stop="runSearch('category:bubbles')" aria-label="Speech bubbles">
-              <i class="fas fa-comment"></i>
-            </div>
+              <div class="btn sexual" @click.prevent.stop="runSearch('category:sexual')" title="Sexual" role="button" tabindex="0">
+                <i class="fas fa-heart"></i>
+              </div>
 
-            <div class="btn actions" @click.prevent.stop="runSearch('category:symbols')" aria-label="Symbols">
-              <i class="fas fa-icons"></i>
-            </div>
+              <div class="btn bubbles" @click.prevent.stop="runSearch('category:bubbles')" title="Speech bubbles" role="button" tabindex="0">
+                <i class="fas fa-comment"></i>
+              </div>
 
-            <div class="btn memes" @click.prevent.stop="runSearch('category:memes')" aria-label="Memes">
-              <i class="fas fa-poo"></i>
-            </div>
+              <div class="btn actions" @click.prevent.stop="runSearch('category:symbols')" title="Symbols" role="button" tabindex="0">
+                <i class="fas fa-icons"></i>
+              </div>
 
-            <div class="btn random" @click.prevent.stop="runSearch('category:random')" aria-label="Random">
-              <i class="fas fa-random"></i>
-            </div>
+              <div class="btn memes" @click.prevent.stop="runSearch('category:memes')" title="Memes" role="button" tabindex="0">
+                <i class="fas fa-poo"></i>
+              </div>
 
-            <div class="btn refresh" @click.prevent.stop="refreshIcons()" aria-label="Refresh eicons">
-              <i class="fas fa-sync"></i>
+              <div class="btn random" @click.prevent.stop="runSearch('category:random')" title="Random" role="button" tabindex="0">
+                <i class="fas fa-random"></i>
+              </div>
+
+              <div class="btn refresh" @click.prevent.stop="refreshIcons()" title="Refresh eicons data" role="button" tabindex="0">
+                <i class="fas fa-sync"></i>
+              </div>
+            </div>
+          </div>
+
+          <div class="courtesy">
+            Courtesy of <a href="https://xariah.net/eicons">xariah.net</a>
+          </div>
+
+          <div class="upload">
+            <a href="https://www.f-list.net/icons.php">Upload eicons</a>
+          </div>
+        </div>
+
+        <div class="carousel slide w-100 results">
+          <div class="carousel-inner w-100" role="listbox">
+            <div class="carousel-item" v-for="eicon in results" role="img" :aria-label="eicon" tabindex="0">
+              <img class="eicon" :alt="eicon" v-lazy="'https://static.f-list.net/images/eicon/' + eicon + '.gif'" :title="eicon" role="button" :aria-label="eicon" @click.prevent.stop="selectIcon(eicon)">
+
+              <div class="btn favorite-toggle" :class="{ favorited: isFavorite(eicon) }" @click.prevent.stop="toggleFavorite(eicon)" role="button" :aria-label="isFavorite(eicon) ? 'Remove from favorites' : 'Add to favorites'">
+                <i class="fas fa-thumbtack"></i>
+              </div>
             </div>
           </div>
         </div>
 
-        <div class="courtesy">
-          Courtesy of <a href="https://xariah.net/eicons">xariah.net</a>
-        </div>
-
-        <div class="upload">
-          <a href="https://www.f-list.net/icons.php">Upload eicons</a>
-        </div>
       </div>
-
-      <div class="carousel slide w-100 results">
-        <div class="carousel-inner w-100" role="listbox">
-          <div class="carousel-item" v-for="eicon in results" role="img" :aria-label="eicon">
-            <img class="eicon" :alt="eicon" :src="'https://static.f-list.net/images/eicon/' + eicon + '.gif'" :title="eicon" @click.prevent.stop="selectIcon(eicon)">
-          </div>
-        </div>
-      </div>
-
     </div>
-  </div>
+  </modal>
 </template>
 
 <script lang="ts">
 import _ from 'lodash';
 import { Component, Hook, Prop, Watch } from '@f-list/vue-ts';
-import Vue from 'vue';
+// import Vue from 'vue';
+import log from 'electron-log'; //tslint:disable-line:match-default-export-name
 import { EIconStore } from '../learn/eicon/store';
+import core from '../chat/core';
+import modal from '../components/Modal.vue';
+import CustomDialog from '../components/custom_dialog';
 
-@Component
-export default class EIconSelector extends Vue {
+@Component({
+  components: { modal }
+})
+export default class EIconSelector extends CustomDialog {
   @Prop
   readonly onSelect?: (eicon: string) => void;
 
@@ -82,8 +98,13 @@ export default class EIconSelector extends Vue {
 
   @Hook('mounted')
   async mounted(): Promise<void> {
-    this.store = await EIconStore.getSharedStore();
-    this.runSearch('');
+    try {
+      this.store = await EIconStore.getSharedStore();
+      this.runSearch('');
+    } catch (err) {
+      // don't break the client in case service is down
+      log.error('eiconSelector.load.error', { err })
+    }
   }
 
   @Watch('search')
@@ -102,15 +123,15 @@ export default class EIconSelector extends Vue {
       const category = s.substring(9).trim();
 
       if (category === 'random') {
-        this.results = _.map(this.store?.random(100), (e) => e.eicon);
+        this.results = _.map(this.store?.random(250), (e) => e.eicon);
       } else {
         this.results = this.getCategoryResults(category);
       }
     } else {
       if (s.length === 0) {
-        this.results = _.map(this.store?.random(100), (e) => e.eicon);
+        this.results = _.map(this.store?.random(250), (e) => e.eicon);
       } else {
-        this.results = _.map(_.take(this.store?.search(s), 100), (e) => e.eicon);
+        this.results = _.map(_.take(this.store?.search(s), 1000), (e) => e.eicon);
       }
     }
   }
@@ -118,19 +139,60 @@ export default class EIconSelector extends Vue {
   getCategoryResults(category: string): string[] {
     switch(category) {
       case 'expressions':
-        return ['coolemoji', 'coughing emoji', 'flushedemoji', 'eyerollemoji', 'laughingemoji', 'grinning emoji', 'party emoji', 'pensiveemoji', 'lipbite emoji', 'nauseous emoji', 'angryemoji', 'facialemoji', 'clapemoji', 'heart eyes', 'kissing heart', 'cowboy emoji', 'cowemoji', 'eggplantemoji', 'peachemoji', 'melting emoji', 'poopemoji', 'thinkingemoji', 'triumphemoji', 'uwuemoji', 'voremoji', 'skullemoji', 'smugemoji', 'heartflooshed', 'fluttersorry', 'snake emoji', 'horseeyes', 'thehorse', 'catblob', 'catblobangery', 'splashemoji', 'tonguemoji', 'blobhugs', 'lickscreen', 'eyes emoji', 'nerdmeme', 'horsepls', 'e62pog', 'thirstytwi', 'bangfingerbang', 'chefs kiss', 'excuse me', 'psychopath', 'ashemote3', 'whentheohitsright', 'caradrinkreact', 'lip_bite', 'twittersob'];
+        return ['coolemoji', 'coughing emoji', 'flushedemoji', 'eyerollemoji', 'laughingemoji', 'grinning emoji', 'party emoji',
+        'pensiveemoji', 'lipbite emoji', 'nauseous emoji', 'angryemoji', 'facialemoji', 'clapemoji', 'heart eyes', 'kissing heart',
+        'cowboy emoji', 'cowemoji', 'eggplantemoji', 'peachemoji', 'melting emoji', 'poopemoji', 'thinkingemoji', 'triumphemoji',
+        'uwuemoji', 'voremoji', 'skullemoji', 'smugemoji', 'heartflooshed', 'blushpanic', 'fluttersorry', 'snake emoji', 'horseeyes', 'thehorse',
+        'catblob', 'catblobangery', 'splashemoji', 'tonguemoji', 'blobhugs', 'lickscreen', 'eyes emoji', 'nerdmeme', 'horsepls',
+        'e62pog', 'thirstytwi', 'bangfingerbang', 'chefs kiss', 'excuse me', 'psychopath', 'ashemote3', 'whentheohitsright',
+        'caradrinkreact', 'lip_bite', 'twittersob', 'confused01', 'blushiedash', 'brogstare', 'brucegrin', 'onefortheteam',
+        'ellesogood', 'speaknoevil',
+        ];
 
       case 'symbols':
-        return ['loveslove', 'pimpdcash', 'pls stop', 'gender-female', 'gender-male', 'gendershemale', 'gender-cuntboy', 'gender-mherm', 'gender-transgender', 'usflag', 'europeflag', 'lgbt', 'transflag', 'yaoilove', 'sunnyuhsuperlove', 'discovered', 'thbun', 'cuckquean', 'goldendicegmgolddicegif', 'pentagramo', 'sexsymbol', 'idnd1', 'instagram', 'twitterlogo', 'snapchaticon', 'tiktok', 'uber', 'google', 'suitclubs', 'suitdiamonds', 'suithearts', 'suitspades'];
+        return ['loveslove', 'pimpdcash', 'pls stop', 'paw2', 'gender-female', 'gender-male', 'gendershemale', 'gender-cuntboy', 'gender-mherm',
+        'gender-transgender', 'usflag', 'europeflag', 'lgbt', 'transflag', 'yaoilove', 'sunnyuhsuperlove', 'discovered', 'thbun',
+        'goldcoin1', 'star', 'full moon', 'sunshine', 'pinetree', 'carrots1', 'smashletter', 'chemicalscience', 'ghostbuster',
+        'cuckquean', 'goldendicegmgolddicegif', 'pentagramo', 'sexsymbol', 'idnd1', 'instagram', 'twitterlogo', 'snapchaticon',
+        'tiktok', 'twitchlogo', 'discord', 'uber', 'google', 'nvidia', 'playstation',
+        'suitclubs', 'suitdiamonds', 'suithearts', 'suitspades', 'chainscuffs',
+        'num-1', 'num-2', 'num-3', 'num-4', 'num-5', 'num-6', 'seven', 'eight', '9ball',
+        'discordeye', 'streamlive', 'check mark', 'x mark', 'question mark', 'lubimark', 'questget',
+        'music', 'cam', 'phone', 'speaker emoji', 'laptop',
+        'naughtyfood', 'open2', 'dont look away', 'milkcartonreal', ];
 
       case 'bubbles':
-        return ['takemetohornyjail', 'notcashmoney', 'lickme', 'iacs', 'imahugeslut', 'fuckyouasshole', 'bubblecute', 'pat my head', 'chorse', 'knotslutbubble', 'toofuckinghot', 'pbmr', 'imabimbo', 'dicefuck', 'ciaig', 'horseslut', 'fatdick', 'tomboypussy', 'breakthesubs', 'fuckingnya', 'iltclion', 'suckfuckobey', 'shemale', 'breedmaster', 'imastepfordwife', 'prier ahegao', 'buttslutbb', 'notgayoranything', 'onlyfans', 'horsecockneed', 'crimes', 'breed143', 'nagagross', 'willrim', 'muskslut', '4lewdbubble', 'thatslewd', 'hypnosiss', 'imahypnoslut', 'sheepsass2', 'imahugeslut', 'ratedmilf', 'notahealslut', 'ratedstud', 'ratedslut', 'xarcuminme', '5lewdbubble', 'xarcumonme', 'choke me', 'iamgoingtopunchyou', 'snapmychoker', 'rude1', 'fuckbun', 'iamindanger', 'fuckingelves', 'slutmug', 'helpicantstopsuckingcocks', 'talkpooltoy', 'thatskindahot', 'simpbait',];
+        return ['takemetohornyjail', 'notcashmoney', 'lickme', 'iacs', 'imahugeslut', 'fuckyouasshole', 'bubblecute', 'pat my head',
+        'chorse', 'knotslutbubble', 'toofuckinghot', 'pbmr', 'imabimbo', 'dicefuck', 'ciaig', 'horseslut', 'fatdick', 'tomboypussy',
+        'breakthesubs', 'fuckingnya', 'iltclion', 'suckfuckobey', 'shemale', 'breedmaster', 'imastepfordwife', 'prier ahegao',
+        'buttslutbb', 'notgayoranything', 'onlyfans', 'horsecockneed', 'crimes', 'breed143', 'nagagross', 'willrim', 'muskslut',
+        '4lewdbubble', 'thatslewd', 'hypnosiss', 'imahypnoslut', 'sheepsass2', 'imahugeslut', 'notahealslut', 'ratedmilf',
+        'ratedstud', 'ratedslut', '5lewdbubble', 'xarcuminme', 'xarcumonme', 'choke me', 'iamgoingtopunchyou', 'snapmychoker',
+        'rude1', 'fuckbun', 'iamindanger', 'fuckingelves', 'slutmug', 'helpicantstopsuckingcocks', 'talkpooltoy', 'thatskindahot', 'ygod',
+        'simpbait', 'eyesuphere', 'fuckpiggy', 'peggable2', 'sydeanothere', 'nothingcan', 'pawslut', 'stupidboys', 'corpsestare-',
+        'dinnersex', 'plappening', 'fallout-standby', 'inbagg', 'request denied', 'goodboy0', 'goodending', 'milky2', 'howbadcanibe',
+        'gwanna', 'spitinmouth', 'bathwater'];
 
       case 'sexual':
-        return ['kissspink', 'paytonkiss', 'coralbutt4', 'slavefidget', 'capstrip', 'pinkundress', 'jhab1', 'caninelover', 'pole', 'rorobutt2', 'fingerlick', 'lapgrind', 'jackthighs', 'a condom', 'wolf abs', 'musclefuck2', 'verobutt3', 'bumsqueeze', 'realahegao4', 'influencerhater', 'assfucker', 'gagged2', 'ballsack3', 'fingering wolf', 'sloppy01', 'sybian', 'femboibate1', 'floppyhorsecock', 'blackshem1', 'fingersucc', 'vullylick', 'freyasuckfingers', 'cmontakeit', 'jessi flash', 'poju-butt', 'cheegrope2', 'patr1', 'ahega01 2', 'handjob1nuke', 'harmanfingers', 'rorysheath2', 'hermione1', '2buttw1', 'dropsqueeze', 'lixlove', 'bbctitjob6', 'appreciativetease', 'bimbolick', 'subj3', 'salivashare', 'ballsworship3', 'wolfsknot2', 'gaykiss', 'slurpkiss', 'absbulge', 'cockiss', 'horsedick11', 'knot1', 'g4ebulge', 'blackadamrough', 'flaunt', 'cummiefj', 'lovetosuck', 'worship', 'hopelessly in love', 'knotts', 'cockloveeee', 'donglove', 'woowyknotjob', 'cummz', 'every drop', 'edgyoops', 'orccummies2', 'oralcreampie100px', 'horseoral9a', 'swallowit', 'sinahegao', 'gayicon2', 'slut4', 'hossspurties2', 'cumringgag', 'jillbimbogiffell2', 'artistry01'];
+        return ['kissspink', 'paytonkiss', 'coralbutt4', 'capstrip', 'pinkundress', 'slavefidget', 'jhab1', 'caninelover', 'pole',
+        'rorobutt2', 'fingerlick', 'lapgrind', 'jackthighs', 'a condom', 'wolf abs', 'musclefuck2', 'verobutt3', 'bumsqueeze',
+        'realahegao4', 'influencerhater', 'assfucker', 'gagged2', 'ballsack3', 'fingering wolf', 'sloppy01', 'sybian', 'femboibate1',
+        'floppyhorsecock', 'blackshem1', 'fingersucc', 'vullylick', 'freyasuckfingers', 'cmontakeit', 'jessi flash', 'poju-butt',
+        'cheegrope2', 'patr1', 'ahega01 2', 'handjob1nuke', 'harmanfingers', 'rorysheath2', 'hermione1', '2buttw1', 'dropsqueeze',
+        'lixlove', 'bbctitjob6', 'appreciativetease', 'bimbolick', 'subj3', 'salivashare', 'ballsworship3', 'wolfsknot2', 'gaykiss',
+        'slurpkiss', 'absbulge', 'cockiss', 'horsedick11', 'knot1', 'g4ebulge', 'blackadamrough', 'dogewank', 'flaunt', 'cummiefj', 'lovetosuck',
+        'worship', 'hopelessly in love', 'knotts', 'cockloveeee', 'donglove', 'woowyknotjob', 'cummz', 'every drop', 'edgyoops',
+        'orccummies2', 'oralcreampie100px', 'horseoral9a', 'swallowit', 'sinahegao', 'gayicon2', 'slut4', 'hossspurties2', 'cumringgag',
+        'jillbimbogiffell2', 'artistry01'];
 
       case 'memes':
-        return ['guncock', 'michaelguns', 'wegotabadass', 'gonnabang', 'flirting101', 'monkeymeme', 'monkeymeme2', 'horsenoises', 'nyancat', 'gayb', 'fortasshole', 'dickletsign', 'sausageface', 'siren0', 'apologize to god', 'jabbalick', 'zeldawink', 'whatislove', 'surprisemothafucka', 'females', 'thanksihateit'];
+        return ['guncock', 'michaelguns', 'wegotabadass', 'gonnabang', 'flirting101', 'monkeymeme', 'monkeymeme2', 'horsenoises',
+        'nyancat', 'gayb', 'fortasshole', 'dickletsign', 'sausageface', 'siren0', 'apologize to god', 'jabbalick', 'zeldawink',
+        'whatislove', 'surprisemothafucka', 'females', 'thanksihateit', 'hell is this', 'confused travolta', 'no words', 'coffindance',
+        'homelander', 'thatsapenis', 'pennyhee', 'kermitbusiness', 'goodbye', 'rickle', 'shiamagic', 'oag', ];
+
+      case 'favorites':
+        return _.keys(core.state.favoriteEIcons);
     }
 
     return [];
@@ -155,80 +217,172 @@ export default class EIconSelector extends Vue {
     (this.$refs['search']  as any).focus();
     (this.$refs['search']  as any).select();
   }
+
+  isFavorite(eicon: string): boolean {
+    return eicon in core.state.favoriteEIcons;
+  }
+
+  toggleFavorite(eicon: string): void {
+    if (eicon in core.state.favoriteEIcons) {
+      delete core.state.favoriteEIcons[eicon];
+    } else {
+      core.state.favoriteEIcons[eicon] = true;
+    }
+
+    void core.settingsStore.set('favoriteEIcons', core.state.favoriteEIcons);
+
+    this.$forceUpdate();
+  }
 }
 </script>
 
 <style lang="scss">
-  .eicon-selector-ui {
-    .loading {
+  .eicon-selector {
+      width: 580px;
+      max-width: 580px;
+      line-height: 1;
+      z-index: 1000;
 
-    }
-
-    .search-bar {
-      display: flex;
-
-      .search {
-        flex: 1;
-        border-top-right-radius: 0;
-        border-bottom-right-radius: 0;
+      &.big {
+        min-height: 530px;
       }
 
-      .search-buttons {
-        margin-left: -1px;
+    .eicon-selector-ui {
+      .loading {
 
-        .btn {
-          border-bottom: 1px solid var(--secondary);
-        }
+      }
 
-        .expressions {
-          border-top-left-radius: 0;
+      .search-bar {
+        display: flex;
+
+        .search {
+          flex: 1;
           border-top-right-radius: 0;
+          border-bottom-right-radius: 0;
         }
 
-        .refresh {
+        .search-buttons {
+          margin-left: -1px;
 
-        }
-
-      }
-    }
-
-    .courtesy {
-      position: absolute;
-      bottom: 5px;
-      font-size: 9px;
-      right: 10px;
-      opacity: 50%;
-    }
-
-    .upload {
-      position: absolute;
-      bottom: 5px;
-      font-size: 9px;
-      left: 10px;
-    }
-
-    .results {
-      max-height: 200px;
-      overflow: hidden;
-      margin-top: 5px;
-
-      .carousel-inner {
-        overflow-x: scroll;
-        overflow-y: hidden;
-
-        .carousel-item {
-          display: table-cell;
-          border: solid 1px transparent !important;
-
-          &:hover {
-            background-color: var(--secondary) !important;
-            border: solid 1px var(--gray-dark) !important;
+          .btn {
+            border-bottom: 1px solid var(--secondary);
           }
 
-          img {
-            width: auto;
-            height: auto;
-            max-height: 75px;
+          .expressions {
+            border-top-left-radius: 0;
+            border-top-right-radius: 0;
+          }
+
+          .refresh {
+
+          }
+
+        }
+      }
+
+      .courtesy {
+        position: absolute;
+        bottom: 7px;
+        font-size: 9px;
+        right: 1rem;
+        opacity: 50%;
+      }
+
+      .upload {
+        position: absolute;
+        bottom: 7px;
+        font-size: 9px;
+        left: 1rem;
+      }
+
+      .results {
+        max-height: 200px;
+        overflow: hidden;
+        margin-top: 5px;
+
+        .carousel-inner {
+          overflow-x: scroll;
+          overflow-y: hidden;
+
+          .carousel-item {
+            display: table-cell;
+            border: solid 1px transparent !important;
+            position: relative;
+
+            &:hover {
+              background-color: var(--secondary) !important;
+              border: solid 1px var(--gray-dark) !important;
+
+              .favorite-toggle {
+                visibility: visible !important;
+              }
+            }
+
+            .favorite-toggle {
+              position: absolute;
+              right: 0;
+              top: 0;
+              border: none;
+              margin: 0;
+              padding: 4px;
+              border-radius: 0;
+              visibility: hidden;
+
+              i {
+                color: var(--gray-dark);
+                opacity: 0.85;
+                -webkit-text-stroke-width: thin;
+                -webkit-text-stroke-color: var(--light);
+
+                &:hover {
+                  opacity: 1;
+                }
+              }
+
+              &.favorited {
+                visibility: visible;
+
+                i {
+                  color: var(--green);
+                  opacity: 1;
+
+                  &:hover {
+                    filter: brightness(1.1);
+                  }
+                }
+              }
+            }
+
+            img.eicon {
+              width: 75px;
+              height: 75px;
+              max-width: 75px;
+              max-height: 75px;
+            }
+          }
+        }
+      }
+    }
+
+    &.big {
+      min-height: 530px;
+      width: 590px;
+      max-width: 590px;
+
+      .eicon-selector-ui {
+        .carousel.results {
+          max-height: unset;
+          height: 535px;
+          margin-bottom: 0.75rem;
+
+          .carousel-inner {
+            overflow-x: hidden;
+            overflow-y: scroll;
+            height: 100%;
+          }
+
+          .carousel-item {
+            display: inline-block;
           }
         }
       }
diff --git a/bbcode/Editor.vue b/bbcode/Editor.vue
index de935c7..d209c92 100644
--- a/bbcode/Editor.vue
+++ b/bbcode/Editor.vue
@@ -11,16 +11,18 @@
             <div class="popover popover-top color-selector" v-show="colorPopupVisible" v-on-clickaway="dismissColorSelector">
                 <div class="popover-body">
                   <div class="btn-group" role="group" aria-label="Color">
-                    <button v-for="btnCol in buttonColors" type="button" class="btn text-color" :class="btnCol" :title="btnCol" @click.prevent.stop="colorApply(btnCol)"></button>
+                    <button v-for="btnCol in buttonColors" type="button" class="btn text-color" :class="btnCol" :title="btnCol" @click.prevent.stop="colorApply(btnCol)" tabindex="0"></button>
                   </div>
                 </div>
             </div>
 
-            <div class="popover popover-top eicon-selector" v-show="eiconPopupVisible" v-on-clickaway="dismissEIconSelector">
-                <div class="popover-body">
-                  <EIconSelector :onSelect="onSelectEIcon" ref="eIconSelector"></EIconSelector>
-                </div>
-            </div>
+<!--            <div class="popover popover-top eicon-selector" :class="{ big: type === 'big' }" v-show="eiconPopupVisible" v-on-clickaway="dismissEIconSelector">-->
+<!--                <div class="popover-body">-->
+<!--                  <EIconSelector :onSelect="onSelectEIcon" ref="eIconSelector"></EIconSelector>-->
+<!--                </div>-->
+<!--            </div>-->
+
+            <EIconSelector :onSelect="onSelectEIcon" ref="eIconSelector"></EIconSelector>
 
             <div class="btn-group toolbar-buttons" style="flex-wrap:wrap">
                 <div v-if="!!characterName" class="character-btn">
@@ -58,7 +60,6 @@
     import {Component, Hook, Prop, Watch} from '@f-list/vue-ts';
     import _ from 'lodash';
     import Vue from 'vue';
-    import { mixin as clickaway } from 'vue-clickaway';
     import {getKey} from '../chat/common';
     import {Keys} from '../keys';
     import {BBCodeElement, CoreBBCodeParser, urlRegex} from './core';
@@ -66,13 +67,13 @@
     import {BBCodeParser} from './parser';
     import {default as IconView} from './IconView.vue';
     import {default as EIconSelector} from './EIconSelector.vue';
+    import Modal from '../components/Modal.vue';
 
     @Component({
       components: {
         'icon': IconView,
         'EIconSelector': EIconSelector
-      },
-      mixins: [ clickaway ]
+      }
     })
     export default class Editor extends Vue {
         @Prop
@@ -102,9 +103,11 @@
         @Prop({default: null})
         readonly characterName: string | null = null;
 
+        @Prop({default: 'normal'})
+        readonly type: 'normal' | 'big' = 'normal';
+
         buttonColors = ['red', 'orange', 'yellow', 'green', 'cyan', 'purple', 'blue', 'pink', 'black', 'brown', 'white', 'gray'];
         colorPopupVisible = false;
-        eiconPopupVisible = false;
 
         preview = false;
         previewWarnings: ReadonlyArray<string> = [];
@@ -194,15 +197,6 @@
               buttons[colorButtonIndex] = colorButton;
             }
 
-            const eiconButtonIndex = _.findIndex(buttons, (b) => b.tag === 'eicon');
-
-            if (this.eiconPopupVisible) {
-              const eiconButton = _.cloneDeep(buttons[eiconButtonIndex]);
-              eiconButton.outerClass = 'toggled';
-
-              buttons[eiconButtonIndex] = eiconButton;
-            }
-
             return buttons;
         }
 
@@ -262,7 +256,10 @@
                 const start = this.text.substr(0, selection.start) + startText;
                 const end = endText + this.text.substr(selection.start);
                 this.text = start + (withInject || '') + end;
-                this.$nextTick(() => this.setSelection(start.length));
+
+                const selectionPoint = withInject ? start.length + withInject.length + endText.length : start.length;
+
+                this.$nextTick(() => this.setSelection(selectionPoint));
             }
             this.$emit('input', this.text);
         }
@@ -280,7 +277,12 @@
         }
 
         dismissEIconSelector(): void {
-          this.eiconPopupVisible = false;
+          (this.$refs['eIconSelector'] as Modal).hide();
+        }
+
+        showEIconSelector(): void {
+          (this.$refs['eIconSelector'] as Modal).show();
+          setTimeout(() => (this.$refs['eIconSelector'] as any).setFocus(), 50);
         }
 
         onSelectEIcon(eiconId: string): void {
@@ -292,20 +294,17 @@
 
           this.applyButtonEffect(button, undefined, eiconId);
 
-          this.eiconPopupVisible = false;
+          this.dismissEIconSelector();
         }
 
         apply(button: EditorButton): void {
+            this.colorPopupVisible = false;
+
             if (button.tag === 'color') {
               this.colorPopupVisible = !this.colorPopupVisible;
               return;
             } else if (button.tag === 'eicon') {
-              this.eiconPopupVisible = !this.eiconPopupVisible;
-
-              if (this.eiconPopupVisible) {
-                setTimeout(() => (this.$refs.eIconSelector as any).setFocus(), 100);
-              }
-
+              this.showEIconSelector();
               return;
             }
 
@@ -446,17 +445,6 @@
       }
     }
 
-    .eicon-selector {
-      width: 550px;
-      max-width: 550px;
-      top: -169px;
-      left: 0;
-      line-height: 1;
-      z-index: 1000;
-      background-color: var(--input-bg);
-      min-height: 170px;
-    }
-
     .color-selector {
       max-width: 145px;
       top: -57px;
diff --git a/chat/ConversationView.vue b/chat/ConversationView.vue
index 720d48e..f9c1b03 100644
--- a/chat/ConversationView.vue
+++ b/chat/ConversationView.vue
@@ -142,6 +142,7 @@
             :hasToolbar="settings.bbCodeBar" ref="textBox" style="position:relative;margin-top:5px"
             :maxlength="isChannel(conversation) || isPrivate(conversation) ? conversation.maxMessageLength : undefined"
             :characterName="ownName"
+            :type="'big'"
             >
 
             <span v-if="isPrivate(conversation) && conversation.typingStatus !== 'clear'" class="chat-info-text">
diff --git a/chat/core.ts b/chat/core.ts
index 4dfbd9c..d918597 100644
--- a/chat/core.ts
+++ b/chat/core.ts
@@ -21,6 +21,7 @@ function createBBCodeParser(): BBCodeParser {
 class State implements StateInterface {
     _settings: Settings | undefined = undefined;
     hiddenUsers: string[] = [];
+    favoriteEIcons: Record<string, boolean> = {};
 
     get settings(): Settings {
         if(this._settings === undefined) throw new Error('Settings load failed.');
@@ -86,6 +87,9 @@ const data = {
 
         const hiddenUsers = await core.settingsStore.get('hiddenUsers');
         state.hiddenUsers = hiddenUsers !== undefined ? hiddenUsers : [];
+
+        const favoriteEIcons = await core.settingsStore.get('favoriteEIcons');
+        state.favoriteEIcons = favoriteEIcons !== undefined ? favoriteEIcons : {};
     }
 };
 
diff --git a/chat/interfaces.ts b/chat/interfaces.ts
index f9a4ec7..f79f9af 100644
--- a/chat/interfaces.ts
+++ b/chat/interfaces.ts
@@ -188,6 +188,7 @@ export namespace Settings {
         recent: Conversation.RecentPrivateConversation[]
         recentChannels: Conversation.RecentChannelConversation[]
         hiddenUsers: string[]
+        favoriteEIcons: Record<string, boolean>
         statusHistory: string[]
         searchHistory: (ExtendedSearchData | SearchData)[]
         hideNonMatchingAds: boolean
@@ -252,6 +253,7 @@ export interface Notifications {
 }
 
 export interface State {
-    settings: Settings
-    hiddenUsers: string[]
+    settings: Settings;
+    hiddenUsers: string[];
+    favoriteEIcons: Record<string, boolean>;
 }
diff --git a/electron/Index.vue b/electron/Index.vue
index a14708c..66ff7e2 100644
--- a/electron/Index.vue
+++ b/electron/Index.vue
@@ -150,6 +150,17 @@
     // import Connection from '../fchat/connection';
     // import Notifications from './notifications';
 
+    import VueLazyload from 'vue-lazyload';
+
+    Vue.use(VueLazyload, {
+      observer: true,
+
+      observerOptions: {
+        rootMargin: '0px',
+        threshold: 0,
+      }
+    });
+
     const webContents = remote.getCurrentWebContents();
     const parent = remote.getCurrentWindow().webContents;
 
diff --git a/package.json b/package.json
index 8d1e106..dd495fc 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
     "vue": "2.6.12",
     "vue-clickaway": "2.2.2",
     "vue-input-tag": "^2.0.7",
+    "vue-lazyload": "1.3.5",
     "vue-loader": "15.9.8",
     "vue-template-compiler": "2.6.12",
     "webpack": "5.8.0"
@@ -79,6 +80,8 @@
     "@cliqz/adblocker-extended-selectors": "1.23.9"
   },
   "scripts": {
-    "postinstall": "electron-rebuild --prebuild-tag-prefix=ignoreprebuilds -f -o keytar"
+    "postinstall": "electron-rebuild --prebuild-tag-prefix=ignoreprebuilds -f -o keytar",
+    "watch": "cd electron && yarn watch",
+    "start": "cd electron && yarn start"
   }
 }
diff --git a/yarn.lock b/yarn.lock
index bf70622..ea14cd9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7706,6 +7706,11 @@ vue-input-tag@^2.0.7:
   dependencies:
     vue "^2.5.17"
 
+vue-lazyload@1.3.5:
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/vue-lazyload/-/vue-lazyload-1.3.5.tgz#eb36d299a519167d987fdf0ebfdc9c6dd1bf1ef0"
+  integrity sha512-SCO/LWgCCbjaregHO4wg2buzITBdPBZRlIS104vERGpT88uxXsK26veuzZpgGAXMR8WpkaR+JDqz80OedpaLiA==
+
 vue-loader@15.9.8:
   version "15.9.8"
   resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.8.tgz#4b0f602afaf66a996be1e534fb9609dc4ab10e61"