flyink13 / VK Top Header Menu

// ==UserScript==
// @name         VK Top Header Menu
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Show top header menu in vk
// @author       Flyink13
// @match        https://*.vk.com/*
// @resource     apiLib https://ifx.su/~va
// @grant        GM_getResourceText
// @copyright 2019, flyink13 (https://openuserjs.org/users/flyink13)
// @updateURL https://openuserjs.org/meta/flyink13/VK_Top_Header_Menu.meta.js
// @license MIT
// ==/UserScript==
/* global gpeByClass, API, nav, showWiki, mentionOver, UiScroll, GM_getResourceText */

function TopHeaderMenu() {
  var styles = `
.top_header_menu {
    height: 42px;
    line-height: 42px;
    cursor: pointer;
    color: #fff;
    float: left;
}

.top_header_menu_wrap {
    position: absolute;
    line-height: normal;
    cursor: default;
    visibility: hidden;
    opacity: 0;
    top: 52px;
    right: -1px;
    background: #fff;
    color: #000;
    border: 1px solid #c5d0db;
    border-top: none;
    border-radius: 0 0 4px 4px;
    box-shadow: 0 20px 40px 0 rgba(0,0,0,.3);
    transition: opacity 100ms linear, top 100ms linear, visibility 100ms linear;
}

.top_header_menu.active .top_header_menu_wrap {
    visibility: visible;
    opacity: 1;
    filter: none;
    top: 42px;
}

.top_header_menu:before {
    content: '';
    width: 6px;
    font-size: 7px;
    height: 6px;
    color: #1d3c5f;
    background: #1d3c5f;
    display: inline-block;
    box-shadow: -1em 0em, -1em -1em, 0em -1em;
    margin: -0.25em 24px;
}

.top_header_menu.active:before {
    color: #fff;
    background: #fff;
}

.top_header_menu_wrap:before {
    position: absolute;
    pointer-events: none;
    border: solid transparent;
    content: '';
    height: 0;
    width: 0;
    right: 23px;
    top: -10px;
}

.top_header_menu_wrap:before {
    border-width: 5px;
    margin: 0 -5px;
    border-bottom-color: #fff;
}

.top_header_menu_content {
    overflow: auto;
    width: 318px;
    height: 250px;
}

.top_header_menu_content {
    align-items: center;
}

.top_header_menu_content .ui_scroll_content {
    padding: 12px;
}

.top_header_menu_content .top_header_link {
    width: 74px;
    display: inline-block;
    overflow: hidden;
    margin: 2px;
    margin-bottom: 0px;
    padding: 10px;
    border-radius: 3px;
    color: #2a5885;
    cursor: pointer;
    text-align: center;
}

.top_header_menu_content a {
    text-decoration: none;
}

.top_header_menu_content .top_header_link:hover {
    box-shadow: 0 0 1px #4a75a7;
}

.top_header_menu_content .top_header_icon {
    width: 50px;
    height: 50px;
    border-radius: 100%;
    margin-bottom: 6px;
    display: inline-block;
    background-position: center;
    position: relative;
    background-size: cover;
}

.top_header_menu_content .top_header_name {
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
    display: block;
}

.top_header_menu input {
    box-shadow: 0px 1px 0px #e7e8ec;
    font-size: 13px;
}


.top_header_icon_counter[title]:after {
    content: attr(title);
    background: #d1d9e0;
    padding: 5px;
    position: absolute;
    bottom: 0px;
    right: 0px;
    border-radius: 3px;
    margin: -3px;
    font-size: 11px;
    text-align: center;
}

.top_header_menu.line_view .top_header_menu_content {
    padding: 0px;
}

.top_header_menu.line_view .top_header_icon {
    width: 32px;
    height: 32px;
    float: left;
    margin-right: 8px;
    margin-bottom: 0px;
}

.top_header_menu.line_view .top_header_name {
    line-height: 32px;
}

.top_header_menu.line_view .top_header_link {
    padding: 8px 12px;
    display: block;
    width: 100%;
    text-align: left;
    box-sizing: border-box;
    margin: 0px;
    margin-bottom: 1px;
    border-radius: 0px;
}

.top_header_menu.line_view .top_header_menu_content .top_header_link:hover {
    box-shadow: none;
    background: #eee;
}
`;

  var code = `
var user_ids = API.fave.getUsers({  }).items@.id;
return {
    app: API.apps.getFromMenu({ filter: 'vk_apps' }).favorites,
    link: API.fave.get({ item_type: 'link' }).items,
    group: API.fave.getPages({ type: 'groups' }).items +
        API.groups.get({ filter: 'admin', extended: 1, fields: 'can_message,is_admin' }).items,
    user: API.users.get({ user_ids: user_ids, fields: 'photo_100,photo_200' }),
};`;

  var linksNames = {
    'https://vk.com/im': 'Сообщения'
  };

  var TopLn = document.getElementById('top_profile_link');
  var MenuB = document.createElement('div');
  var MenuW = document.createElement('div');
  var MenuC = document.createElement('div');
  var MenuS = document.createElement('input');
  var StyEl = document.createElement('style');

  if (!TopLn) return;

  MenuS.placeholder = 'Поиск';
  MenuS.autocomplete = 'off';
  MenuS.spellcheck = 'off';
  StyEl.innerHTML = styles;
  MenuB.className = 'top_header_menu top_nav_btn head_nav_item fl_r';
  MenuW.className = 'top_header_menu_wrap';
  MenuC.className = 'top_header_menu_content';
  MenuS.className = 'ui_search_field _field';

  function updateCounter(el, timeLimit) {
    if (!/top_header_icon_counter/.test(el.firstChild.className)) {
      return;
    }

    if (Date.now() - el.lastUpdate < timeLimit) {
      return;
    }
    el.lastUpdate = Date.now();

    var group_id = el.href.match(/\d+/)[0];
    API('messages.getConversations', {
      filter: 'unread',
      group_id: group_id,
    }).then((r) => {
      var count = r.response.count;
      if (count) {
        el.firstChild.title = count;
      } else {
        delete el.firstChild.title;
      }
    });
  }

  function getMenuItems() {
    return Array.from(MenuC.querySelectorAll('.top_header_link'));
  }

  function updateCounters() {
    getMenuItems().forEach((el) => {
      updateCounter(el, 30e3);
    });
  }

  function search() {
    var reg = new RegExp(MenuS.value, 'i');
    getMenuItems().forEach((el) => {
      if (MenuS.value === '' || reg.test(el.textContent)) {
        el.style.display = '';
      } else {
        el.style.display = 'none';
      }
    });
  }

  MenuS.onkeyup = search;
  MenuS.onchange = search;
  document.body.addEventListener('mousedown', (e) => {
    var el = e.target.firstChild || e.target;
    var toMenu = gpeByClass('top_header_menu', el);
    var toWrap = gpeByClass('top_header_menu_wrap', el);

    if (toWrap) return;
    if (toMenu) {
      if (!MenuB.classList.contains('active')) {
        MenuS.value = '';
        search();
        updateCounters();
        if (MenuC._scroll) {
          MenuC._scroll.scrollTop(0);
        }
      }

      MenuB.classList.toggle('active');
      setTimeout(() => MenuS.focus(), 100);
      return;
    }
    MenuB.classList.remove('active');
  });

  MenuB.appendChild(MenuW);
  MenuW.appendChild(MenuS);
  MenuW.appendChild(MenuC);
  MenuB.appendChild(StyEl);

  API('execute', {code, v: '5.98'}).then((res) => {
    var app_ids = [];
    var favs = res.response;

    var appsEls = [].map.call(document.querySelectorAll('.genre_app a'), (link) => {
      var app_id = 1 * link.href.match(/\d+/)[0];
      app_ids.push(app_id);
    });

    if (!appsEls || !appsEls.length) {
      return favs;
    }

    return API('apps.get', {
      app_ids,
      extended: 1
    }).then((res) => {
      var webApps = res.response.items;
      favs.app = favs.app.concat(webApps);
      return favs;
    });
  }).then((favs) => {
    console.log(favs);
    var df = document.createDocumentFragment();
    Object.keys(favs).forEach((type) => {
      favs[type].forEach((fav) => {
        var favWrap = document.createElement('a');
        var favImg = document.createElement('div');
        var favName = document.createElement('a');

        favName.className = 'top_header_name';
        favWrap.className = 'top_header_link';
        favImg.className = 'top_header_icon';
        favWrap.onclick = () => {
          MenuB.classList.remove('active');
          return nav.go(favWrap.href);
        };
        favImg.onmouseover = () => updateCounter(favWrap, 10e3);

        switch (type) {
          case 'app':
            favImg.src = fav.icon_150 || '/images/vkapp_d.png';
            favName.textContent = fav.title;
            favWrap.href = '/app' + fav.id;
            favWrap.onclick = () => {
              MenuB.classList.remove('active');
              return showWiki({
                w: 'app' + fav.id
              });
            };
            break;
          case 'group':
            Object.assign(fav, fav.group || {});
            favWrap.href = '/club' + fav.id;
            favName.textContent = fav.name;
            favImg.src = fav.photo_200 || '/images/camera_200.png';
            favName.setAttribute('mention_id', 'club' + fav.id);
            favName.onmouseover = (e) => mentionOver(favName, {shift: [20, 7, 7]});
            if (fav.can_message && fav.is_admin) {
              favImg.className += ' top_header_icon_counter';
            }
            break;
          case 'user':
            favWrap.href = '/id' + fav.id;
            favName.textContent = fav.first_name + ' ' + fav.last_name;
            favImg.src = fav.photo_200 || fav.photo_100 || '/images/camera_200.png';
            favName.setAttribute('mention_id', 'id' + fav.id);
            favName.onmouseover = (e) => mentionOver(favName, {shift: [20, 7, 7]});
            break;
          case 'link':
            Object.assign(fav, fav.link);
            favWrap.href = fav.url;
            favImg.src = fav.photo ? fav.photo.sizes[0].url : '/images/camera_200.png?ava=1';
            favName.textContent = linksNames[fav.title] || fav.title;
            break;
        }

        favImg.style.backgroundImage = 'url(' + favImg.src + ')';

        favWrap.appendChild(favImg);
        favWrap.appendChild(favName);
        df.appendChild(favWrap);
      });
    });
    MenuC.appendChild(df);
  }).catch((e) => {
    console.error(e);
    MenuC.textContent = 'Ошибка загрузки страницы';
  }).then(() => {
    TopLn.parentNode.insertBefore(MenuB, TopLn);
    MenuC._scroll = new UiScroll(MenuC, {
      shadows: true,
      global: true,
    });
  });
}

(function injectScript() {
  var script = document.createElement('script');
  var code = '';
  code += '(function(){' + (GM_getResourceText('apiLib')) + '})();';
  code += '(' + TopHeaderMenu + ')();';
  script.appendChild(document.createTextNode(code));
  (document.body || document.head || document.documentElement).appendChild(script);
})();