Initial autocomplete and all emotes

This commit is contained in:
Jiiks 2018-03-10 05:05:12 +02:00
parent 27aa21a47a
commit 523d226b91
11 changed files with 232 additions and 25 deletions

View File

@ -0,0 +1,33 @@
/**
* BetterDiscord Autocomplete Component
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
<template>
<div class="bd-autocomplete">
<div class="bd-autocomplete-inner">
<div class="bd-autocompleteRow">
<div class="bd-autocompleteSelector">
<div class="bd-autocompleteTitle">
Emotes Matching:
<strong>Kappa</strong>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import EmoteModule from '../../../builtin/Emotemodule.js';
export default {
props: ['title', 'emotes'],
beforeMount() {
console.log(EmoteModule);
}
}
</script>

View File

@ -7,18 +7,18 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { Events } from 'modules';
import { FileUtils } from 'common';
import { Events, Globals } from 'modules';
import { DOM, VueInjector } from 'ui';
import EditedTimeStamp from './EditedTimeStamp.vue';
import EmoteComponent from './EmoteComponent.vue';
import TwitchEmotes from '../data/twitch_emotes.json';
let emotes = null;
export default class {
static observe() {
static async observe() {
const dataPath = Globals.getObject('paths').find(path => path.id === 'data').path;
emotes = await FileUtils.readJsonFromFile(dataPath + '/emotes.json');
window.emotee = emotes;
Events.on('ui:mutable:.markup', markup => {
this.injectEmotes(markup);
});
@ -84,11 +84,19 @@ export default class {
}
static isEmote(word) {
if (!emotes) return null;
const name = word.replace(/:/g, '');
if (TwitchEmotes.hasOwnProperty(name)) {
const src = `https://static-cdn.jtvnw.net/emoticons/v1/${TwitchEmotes[name]}/1.0`;
return { name, src };
}
return null;
const emote = emotes.find(emote => emote.id === name);
if (!emote) return null;
let { id, value } = emote;
if (value.id) value = value.id;
const uri = emote.type === 2 ? 'https://cdn.betterttv.net/emote/:id/1x' : emote.type === 1 ? 'https://cdn.frankerfacez.com/emoticon/:id/1' : 'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0';
return { name, src: uri.replace(':id', value) };
}
static filterTest() {
const re = new RegExp('Kappa', 'i');
const filtered = emotes.filter(emote => re.test(emote.id));
return filtered.slice(0, 10);
}
}

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,7 @@ import BdCss from './styles/index.scss';
import { Events, CssEditor, Globals, ExtModuleManager, PluginManager, ThemeManager, ModuleManager, WebpackModules, Settings, Database } from 'modules';
import { ClientLogger as Logger, ClientIPC } from 'common';
import { EmoteModule } from 'builtin';
const ignoreExternal = false;
const ignoreExternal = true;
class BetterDiscord {
@ -31,7 +31,6 @@ class BetterDiscord {
window.bdlogs = Logger;
window.emotes = EmoteModule;
window.dom = DOM;
EmoteModule.observe();
DOM.injectStyle(BdCss, 'bdmain');
Events.on('global-ready', this.globalReady.bind(this));
@ -52,6 +51,7 @@ class BetterDiscord {
Modals.showContentManagerErrors();
Events.emit('ready');
Events.emit('discord-ready');
EmoteModule.observe();
} catch (err) {
Logger.err('main', ['FAILED TO LOAD!', err]);
}

View File

@ -0,0 +1,76 @@
.bd-autocomplete {
border-radius: 5px 5px 0 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
z-index: 3;
bottom: 100%;
left: 0;
position: absolute;
right: 0;
background-color: #2f3136;
.bd-autocomplete-inner {
padding-bottom: 8px;
white-space: nowrap;
.bd-autocompleteRow {
padding: 0 8px;
font-size: 14px;
line-height: 16px;
.bd-autocompleteSelector {
border-radius: 3px;
padding: 8px;
&.bd-selectable {
cursor: pointer;
}
&.bd-selected {
background-color: #36393f;
}
.bd-autocompleteTitle {
color: #72767d;
padding: 4px 0;
text-transform: uppercase;
font-weight: 600;
line-height: 16px;
font-size: 12px;
strong {
color: #fff;
text-transform: none;
font-weight: 500;
}
}
.bd-autocompleteField {
display: flex;
flex: 1 1 auto;
color: #f6f6f7;
min-height: 16px;
-webkit-box-direction: normal;
-webkit-box-orient: horizontal;
flex-direction: row;
flex-wrap: nowrap;
-webkit-box-pack: start;
justify-content: flex-start;
-webkit-box-align: center;
align-items: center;
img {
min-width: 16px;
width: 16px;
}
div {
margin-left: 8px;
color: #f6f6f7;
}
}
}
}
}
}

View File

@ -8,3 +8,4 @@
@import './drawers.scss';
@import './preformatted.scss';
@import './refreshbtn.scss';
@import './autocomplete.scss';

View File

@ -13,6 +13,7 @@ import Reflection from './reflection';
import DOM from './dom';
import VueInjector from './vueinjector';
import EditedTimeStamp from './components/common/EditedTimestamp.vue';
import Autocomplete from './components/common/Autocomplete.vue';
class TempApi {
static get currentGuildId() {
@ -42,6 +43,7 @@ export default class extends EventListener {
constructor() {
super();
window.injectAc = this.injectAutocomplete;
const messageFilter = function (m) {
return m.addedNodes && m.addedNodes.length && m.addedNodes[0].classList && m.addedNodes[0].classList.contains('message-group');
}
@ -195,4 +197,18 @@ export default class extends EventListener {
get appMount() {
return document.getElementById('app-mount');
}
injectAutocomplete() {
const root = document.createElement('span');
const parent = document.querySelector('[class*="channelTextArea"] > [class*="inner"]');
if (!parent) return;
parent.append(root);
VueInjector.inject(
root,
DOM.createElement('span'),
{ Autocomplete },
`<Autocomplete/>`,
true
);
}
}

View File

@ -0,0 +1,53 @@
/**
* BetterDiscord Autocomplete Component
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
<template>
<div class="bd-autocomplete">
<div v-if="emotes && emotes.length" class="bd-autocomplete-inner">
<div class="bd-autocompleteRow">
<div class="bd-autocompleteSelector">
<div class="bd-autocompleteTitle">
Emotes Matching:
<strong>Kappa</strong>
</div>
</div>
</div>
<div v-for="emote in emotes" class="bd-autocompleteRow">
<div class="bd-autocompleteSelector">
<div class="bd-autocompleteField">
<img :src="getEmoteSrc(emote)"/>
<div>{{emote.id}}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { EmoteModule } from 'builtin';
export default {
data() {
return {
emotes: []
}
},
beforeMount() {
this.emotes = EmoteModule.filterTest();
},
methods: {
getEmoteSrc(emote) {
let { id, value } = emote;
if (value.id) value = value.id;
const uri = emote.type === 2 ? 'https://cdn.betterttv.net/emote/:id/1x' : emote.type === 1 ? 'https://cdn.frankerfacez.com/emoticon/:id/1' : 'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0';
return uri.replace(':id', value);
}
}
}
</script>

View File

@ -12,13 +12,16 @@ import Vue from './vue';
export default class {
static inject(root, bdnode, components, template, replaceRoot) {
static inject(root, bdnode, components, template, replaceRoot, data) {
if(!replaceRoot) bdnode.appendTo(root);
return new Vue({
el: replaceRoot ? root : bdnode.element,
components,
template
template,
data: {
data
}
});
}

View File

@ -41,9 +41,7 @@ const dummyArgs = {
{ 'id': 'modules', 'path': __modulePath }
]
};
const dbInstance = new Database(dummyArgs.paths.find(path => path.id === 'data').path + '/storage');
const dbInstance = new Database(dummyArgs.paths.find(path => path.id === 'data').path);
console.log(dummyArgs);

View File

@ -15,23 +15,41 @@ class Database {
constructor(dbPath) {
this.exec = this.exec.bind(this);
this.update = this.update.bind(this);
this.db = new Datastore({ filename: dbPath, autoload: true });
this.init(dbPath);
//this.db.emotes.loadDatabase();
//this.db = new Datastore({ filename: dbPath, autoload: true });
}
async init(dbPath) {
this.db = {
storage: new Datastore({ filename: `${dbPath}/storage`, autoload: true })
};
}
async update(cmd) {
const db = cmd.db ? this.db[cmd.db] : this.db.storage;
return new Promise((resolve, reject) => {
this.db.update(cmd.args, cmd.data, { upsert: true }, (err, docs) => {
db.update(cmd.args, cmd.data, { upsert: true }, (err, docs) => {
if (err) return reject(err);
this.db.persistence.compactDatafile();
db.persistence.compactDatafile();
resolve(docs);
});
});
}
async find(cmd) {
console.log('FIND', cmd);
async loadDatabase(db) {
return new Promise((resolve, reject) => {
this.db.find(cmd.args, (err, docs) => {
db.loadDatabase();
resolve();
});
}
async find(cmd) {
const db = cmd.db ? this.db[cmd.db] : this.db.storage;
let args = cmd.args;
if (cmd.regex) args = { [cmd.regex.param]: new RegExp(cmd.regex.source, cmd.regex.flags) };
return new Promise((resolve, reject) => {
db.find(args, (err, docs) => {
if (err) return reject(err);
resolve(docs);
});