From 47a4416df21412a9184a9b5045233260c1cb80bb Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 21 Jun 2020 22:05:17 +0200 Subject: dots v2 --- .cache/wal/chromium/Cached Theme.pak | Bin 0 -> 1372 bytes .cache/wal/chromium/bg.png | Bin 0 -> 495 bytes .cache/wal/chromium/manifest.json | 90 + .cache/wal/colors | 16 + .cache/wal/colors-kitty.conf | 20 + .cache/wal/colors-konsole.colorscheme | 63 + .cache/wal/colors-oomox | 17 + .cache/wal/colors-putty.reg | 26 + .cache/wal/colors-rofi-dark.rasi | 161 ++ .cache/wal/colors-rofi-light.rasi | 161 ++ .cache/wal/colors-speedcrunch.json | 15 + .cache/wal/colors-sway | 21 + .cache/wal/colors-tty.sh | 19 + .cache/wal/colors-wal-dmenu.h | 6 + .cache/wal/colors-wal-dwm.h | 18 + .cache/wal/colors-wal-st.h | 34 + .cache/wal/colors-wal-tabbed.h | 6 + .cache/wal/colors-wal.vim | 23 + .cache/wal/colors-waybar.css | 20 + .cache/wal/colors.Xresources | 68 + .cache/wal/colors.css | 28 + .cache/wal/colors.hs | 37 + .cache/wal/colors.json | 28 + .cache/wal/colors.scss | 26 + .cache/wal/colors.sh | 36 + .cache/wal/colors.yml | 24 + .cache/wal/sequences | 1 + .cache/wal/wal | 1 + .config/BetterDiscord/plugins/BDFDB.config.json | 5 + .../plugins/BadgesEverywhere.config.json | 29 + .../plugins/BetterSearchPage.config.json | 7 + .../plugins/BetterSearchPage.plugin.js | 194 ++ .../BetterDiscord/plugins/EditChannels.config.json | 17 + .../BetterDiscord/plugins/EditChannels.plugin.js | 773 ++++++ .../BetterDiscord/plugins/EditServers.config.json | 28 + .../BetterDiscord/plugins/EditServers.plugin.js | 1079 ++++++++ .../plugins/NotificationSounds.config.json | 356 +++ .../plugins/NotificationSounds.plugin.js | 546 ++++ .config/BetterDiscord/plugins/PinDMs.config.json | 40 + .config/BetterDiscord/plugins/PinDMs.plugin.js | 1149 +++++++++ .../BetterDiscord/plugins/QuickMention.plugin.js | 78 + .../plugins/RemoveNicknames.config.json | 17 + .../plugins/RemoveNicknames.plugin.js | 201 ++ .../plugins/SendLargeMessages.plugin.js | 259 ++ .../themes/Material-Discord.theme.css | 3 + .config/BetterDiscord/themes/MinimalCord.theme.css | 138 + .config/BetterDiscord/themes/glass.theme.css | 40 + .config/BetterDiscord/themes/pywal.theme.css | 75 + .config/brave-flags.conf | 1 + .config/chromium-flags.conf | 1 + .config/coc/commands | 3 + .config/coc/extensions/package.json | 14 + .config/coc/snippets-mru | 9 + .config/coc/ultisnips/css.snippets | 5 + .config/coc/ultisnips/html.snippets | 16 + .config/coc/ultisnips/tex.snippets | 34 + .config/i3/config | 165 ++ .config/i3/config.backup | 140 + .config/konsolerc | 25 + .config/nvim/autoload/plug.vim | 2665 ++++++++++++++++++++ .config/nvim/autoload/plug.vim.old | 2597 +++++++++++++++++++ .config/nvim/coc-settings.json | 3 + .config/nvim/ftplugin/tex.vim | 4 + .config/nvim/init.vim | 246 ++ .config/nvim/links.sh | 9 + .config/nvim/pywal/base.vim | 19 + .config/nvim/pywal/colors.vim | 46 + .config/nvim/pywal/pywal.vim | 65 + .config/picom.conf | 70 + .config/polybar/config.ini | 358 +++ .config/polybar/onstart.sh | 3 + .config/polybar/player-mpris-simple.sh | 9 + .config/rofi/config | 17 + .config/rofi/pywal.rasi | 40 + .config/spicetify/Themes/Loekaars/README.md | 11 + .config/spicetify/Themes/Loekaars/color.ini | 18 + .config/spicetify/Themes/Loekaars/user.css | 630 +++++ .config/spicetify/config.ini | 36 + .config/startpage/index.html | 45 + .config/startpage/jquery.min.js | 2 + .config/startpage/pywal.css | 63 + .config/startpage/script.js | 7 + .config/startpage/style.css | 91 + .config/user-dirs.dirs | 15 + .config/user-dirs.locale | 1 + .config/zathura/zathurarc | 13 + .local/share/bin/brave | 9 + .local/share/bin/loadwall | 5 + .local/share/bin/m3uprefix | 9 + .local/share/konsole/Loekaars.profile | 35 + .local/share/konsole/colors-konsole.colorscheme | 1 + .xprofile | 9 + .zshrc | 117 + readme.md | 24 + 94 files changed, 13704 insertions(+) create mode 100644 .cache/wal/chromium/Cached Theme.pak create mode 100644 .cache/wal/chromium/bg.png create mode 100644 .cache/wal/chromium/manifest.json create mode 100644 .cache/wal/colors create mode 100644 .cache/wal/colors-kitty.conf create mode 100644 .cache/wal/colors-konsole.colorscheme create mode 100644 .cache/wal/colors-oomox create mode 100644 .cache/wal/colors-putty.reg create mode 100644 .cache/wal/colors-rofi-dark.rasi create mode 100644 .cache/wal/colors-rofi-light.rasi create mode 100644 .cache/wal/colors-speedcrunch.json create mode 100644 .cache/wal/colors-sway create mode 100644 .cache/wal/colors-tty.sh create mode 100644 .cache/wal/colors-wal-dmenu.h create mode 100644 .cache/wal/colors-wal-dwm.h create mode 100644 .cache/wal/colors-wal-st.h create mode 100644 .cache/wal/colors-wal-tabbed.h create mode 100644 .cache/wal/colors-wal.vim create mode 100644 .cache/wal/colors-waybar.css create mode 100644 .cache/wal/colors.Xresources create mode 100644 .cache/wal/colors.css create mode 100644 .cache/wal/colors.hs create mode 100644 .cache/wal/colors.json create mode 100644 .cache/wal/colors.scss create mode 100644 .cache/wal/colors.sh create mode 100644 .cache/wal/colors.yml create mode 100644 .cache/wal/sequences create mode 100644 .cache/wal/wal create mode 100644 .config/BetterDiscord/plugins/BDFDB.config.json create mode 100644 .config/BetterDiscord/plugins/BadgesEverywhere.config.json create mode 100644 .config/BetterDiscord/plugins/BetterSearchPage.config.json create mode 100644 .config/BetterDiscord/plugins/BetterSearchPage.plugin.js create mode 100644 .config/BetterDiscord/plugins/EditChannels.config.json create mode 100644 .config/BetterDiscord/plugins/EditChannels.plugin.js create mode 100644 .config/BetterDiscord/plugins/EditServers.config.json create mode 100644 .config/BetterDiscord/plugins/EditServers.plugin.js create mode 100644 .config/BetterDiscord/plugins/NotificationSounds.config.json create mode 100644 .config/BetterDiscord/plugins/NotificationSounds.plugin.js create mode 100644 .config/BetterDiscord/plugins/PinDMs.config.json create mode 100644 .config/BetterDiscord/plugins/PinDMs.plugin.js create mode 100644 .config/BetterDiscord/plugins/QuickMention.plugin.js create mode 100644 .config/BetterDiscord/plugins/RemoveNicknames.config.json create mode 100644 .config/BetterDiscord/plugins/RemoveNicknames.plugin.js create mode 100644 .config/BetterDiscord/plugins/SendLargeMessages.plugin.js create mode 100644 .config/BetterDiscord/themes/Material-Discord.theme.css create mode 100644 .config/BetterDiscord/themes/MinimalCord.theme.css create mode 100644 .config/BetterDiscord/themes/glass.theme.css create mode 100644 .config/BetterDiscord/themes/pywal.theme.css create mode 100644 .config/brave-flags.conf create mode 100644 .config/chromium-flags.conf create mode 100644 .config/coc/commands create mode 100644 .config/coc/extensions/package.json create mode 100644 .config/coc/snippets-mru create mode 100644 .config/coc/ultisnips/css.snippets create mode 100644 .config/coc/ultisnips/html.snippets create mode 100644 .config/coc/ultisnips/tex.snippets create mode 100644 .config/i3/config create mode 100644 .config/i3/config.backup create mode 100644 .config/konsolerc create mode 100644 .config/nvim/autoload/plug.vim create mode 100644 .config/nvim/autoload/plug.vim.old create mode 100644 .config/nvim/coc-settings.json create mode 100644 .config/nvim/ftplugin/tex.vim create mode 100644 .config/nvim/init.vim create mode 100644 .config/nvim/links.sh create mode 100644 .config/nvim/pywal/base.vim create mode 100644 .config/nvim/pywal/colors.vim create mode 100644 .config/nvim/pywal/pywal.vim create mode 100644 .config/picom.conf create mode 100644 .config/polybar/config.ini create mode 100644 .config/polybar/onstart.sh create mode 100755 .config/polybar/player-mpris-simple.sh create mode 100644 .config/rofi/config create mode 100644 .config/rofi/pywal.rasi create mode 100644 .config/spicetify/Themes/Loekaars/README.md create mode 100644 .config/spicetify/Themes/Loekaars/color.ini create mode 100644 .config/spicetify/Themes/Loekaars/user.css create mode 100644 .config/spicetify/config.ini create mode 100644 .config/startpage/index.html create mode 100644 .config/startpage/jquery.min.js create mode 100644 .config/startpage/pywal.css create mode 100644 .config/startpage/script.js create mode 100644 .config/startpage/style.css create mode 100644 .config/user-dirs.dirs create mode 100644 .config/user-dirs.locale create mode 100644 .config/zathura/zathurarc create mode 100755 .local/share/bin/brave create mode 100755 .local/share/bin/loadwall create mode 100755 .local/share/bin/m3uprefix create mode 100644 .local/share/konsole/Loekaars.profile create mode 120000 .local/share/konsole/colors-konsole.colorscheme create mode 100755 .xprofile create mode 100644 .zshrc create mode 100644 readme.md diff --git a/.cache/wal/chromium/Cached Theme.pak b/.cache/wal/chromium/Cached Theme.pak new file mode 100644 index 0000000..f075cfe Binary files /dev/null and b/.cache/wal/chromium/Cached Theme.pak differ diff --git a/.cache/wal/chromium/bg.png b/.cache/wal/chromium/bg.png new file mode 100644 index 0000000..a3058f4 Binary files /dev/null and b/.cache/wal/chromium/bg.png differ diff --git a/.cache/wal/chromium/manifest.json b/.cache/wal/chromium/manifest.json new file mode 100644 index 0000000..6ae3e9a --- /dev/null +++ b/.cache/wal/chromium/manifest.json @@ -0,0 +1,90 @@ +{ + "description": "Colorscheme generated by jswal", + "manifest_version": 2, + "name": "pywal", + "theme": { + "images": { + "theme_frame": "bg.png" + }, + "colors": { + "bookmark_text": [ + 196, + 196, + 196 + ], + "frame": [ + 255, + 255, + 255 + ], + "ntp_background": [ + 19, + 19, + 21 + ], + "ntp_text": [ + 19, + 19, + 21 + ], + "tab_background_text": [ + 137, + 137, + 137 + ], + "tab_background_text_inactive": [ + 137, + 137, + 137 + ], + "tab_background_text_incognito": [ + 137, + 137, + 137 + ], + "tab_background_text_incognito_inactive": [ + 137, + 137, + 137 + ], + "tab_text": [ + 196, + 196, + 196 + ], + "toolbar": [ + 26, + 26, + 27 + ], + "button_background": [ + 255, + 255, + 255 + ] + }, + "tints": { + "buttons": [ + 0.0972222222222219, + 11.76470588235294, + 40 + ], + "frame_inactive": [ + -1, + -1, + -1 + ], + "frame_incognito": [ + -1, + -1, + -1 + ], + "frame_incognito_inactive": [ + -1, + -1, + -1 + ] + } + }, + "version": "2" +} \ No newline at end of file diff --git a/.cache/wal/colors b/.cache/wal/colors new file mode 100644 index 0000000..e28d163 --- /dev/null +++ b/.cache/wal/colors @@ -0,0 +1,16 @@ +#141415 +#292b37 +#3c2a27 +#3b3033 +#3f3330 +#4b4240 +#66615a +#c4c4c4 +#4e4e4f +#292b37 +#3c2a27 +#3b3033 +#3f3330 +#4b4240 +#66615a +#c4c4c4 diff --git a/.cache/wal/colors-kitty.conf b/.cache/wal/colors-kitty.conf new file mode 100644 index 0000000..a56601f --- /dev/null +++ b/.cache/wal/colors-kitty.conf @@ -0,0 +1,20 @@ +foreground #c4c4c4 +background #141415 +cursor #c4c4c4 + +color0 #141415 +color8 #4e4e4f +color1 #292b37 +color9 #292b37 +color2 #3c2a27 +color10 #3c2a27 +color3 #3b3033 +color11 #3b3033 +color4 #3f3330 +color12 #3f3330 +color5 #4b4240 +color13 #4b4240 +color6 #66615a +color14 #66615a +color7 #c4c4c4 +color15 #c4c4c4 diff --git a/.cache/wal/colors-konsole.colorscheme b/.cache/wal/colors-konsole.colorscheme new file mode 100644 index 0000000..c09a9de --- /dev/null +++ b/.cache/wal/colors-konsole.colorscheme @@ -0,0 +1,63 @@ +[Background] +Color=20,20,21 + +[BackgroundIntense] +Color=20,20,21 + +[Color0] +Color=20,20,21 + +[Color0Intense] +Color=78,78,79 + +[Color1] +Color=88,91,113 + +[Color1Intense] +Color=88,91,113 + +[Color2] +Color=116,85,80 + +[Color2Intense] +Color=116,85,80 + +[Color3] +Color=103,86,90 + +[Color3Intense] +Color=103,86,90 + +[Color4] +Color=102,84,80 + +[Color4Intense] +Color=102,84,80 + +[Color5] +Color=75,66,64 + +[Color5Intense] +Color=75,66,64 + +[Color6] +Color=102,97,90 + +[Color6Intense] +Color=102,97,90 + +[Color7] +Color=196,196,196 + +[Color7Intense] +Color=196,196,196 + +[Foreground] +Color=196,196,196 + +[ForegroundIntense] +Color=196,196,196 + +[General] +Description=Colorscheme generated by wal +Opacity=0.92 \ No newline at end of file diff --git a/.cache/wal/colors-oomox b/.cache/wal/colors-oomox new file mode 100644 index 0000000..806f6b6 --- /dev/null +++ b/.cache/wal/colors-oomox @@ -0,0 +1,17 @@ +NAME=wal +BG=141415 +FG=c4c4c4 +MENU_BG=141415 +MENU_FG=c4c4c4 +SEL_BG=292b37 +SEL_FG=141415 +TXT_BG=141415 +TXT_FG=c4c4c4 +BTN_BG=3c2a27 +BTN_FG=c4c4c4 +HDR_BTN_BG=3b3033 +HDR_BTN_FG=c4c4c4 +GTK3_GENERATE_DARK=True +ROUNDNESS=0 +SPACING=3 +GRADIENT=0.0 diff --git a/.cache/wal/colors-putty.reg b/.cache/wal/colors-putty.reg new file mode 100644 index 0000000..2708554 --- /dev/null +++ b/.cache/wal/colors-putty.reg @@ -0,0 +1,26 @@ +Windows Registry Editor Version 5.00 + +[HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions\wal] +"Colour0"="196,196,196" ; Default Foreground +"Colour1"="196,196,196" ; Default Bold Foreground +"Colour2"="20,20,21" ; Default Background +"Colour3"="20,20,21" ; Default Bold Background +"Colour4"="20,20,21" ; Cursor Text +"Colour5"="196,196,196" ; Cursor Color +"Colour6"="20,20,21" ; ANSI Black +"Colour7"="78,78,79" ; ANSI Black Bold +"Colour8"="41,43,55" ; ANSI Red +"Colour9"="41,43,55" ; ANSI Red Bold +"Colour10"="60,42,39" ; ANSI Green +"Colour11"="60,42,39" ; ANSI Green Bold +"Colour12"="59,48,51" ; ANSI Yellow +"Colour13"="59,48,51" ; ANSI Yellow Bold +"Colour14"="63,51,48" ; ANSI Blue +"Colour15"="63,51,48" ; ANSI Blue Bold +"Colour16"="75,66,64" ; ANSI Magenta +"Colour17"="75,66,64" ; ANSI Magenta Bold +"Colour18"="102,97,90" ; ANSI Cyan +"Colour19"="102,97,90" ; ANSI Cyan Bold +"Colour20"="196,196,196" ; ANSI White +"Colour21"="196,196,196" ; ANSI White Bold + diff --git a/.cache/wal/colors-rofi-dark.rasi b/.cache/wal/colors-rofi-dark.rasi new file mode 100644 index 0000000..77b916d --- /dev/null +++ b/.cache/wal/colors-rofi-dark.rasi @@ -0,0 +1,161 @@ +* { + active-background: #3c2a27; + active-foreground: @foreground; + normal-background: @background; + normal-foreground: @foreground; + urgent-background: #292b37; + urgent-foreground: @foreground; + + alternate-active-background: @background; + alternate-active-foreground: @foreground; + alternate-normal-background: @background; + alternate-normal-foreground: @foreground; + alternate-urgent-background: @background; + alternate-urgent-foreground: @foreground; + + selected-active-background: #292b37; + selected-active-foreground: @foreground; + selected-normal-background: #3c2a27; + selected-normal-foreground: @foreground; + selected-urgent-background: #3b3033; + selected-urgent-foreground: @foreground; + + background-color: @background; + background: #141415; + foreground: #c4c4c4; + border-color: @background; + spacing: 2; +} + +#window { + background-color: @background; + border: 0; + padding: 2.5ch; +} + +#mainbox { + border: 0; + padding: 0; +} + +#message { + border: 2px 0px 0px; + border-color: @border-color; + padding: 1px; +} + +#textbox { + text-color: @foreground; +} + +#inputbar { + children: [ prompt,textbox-prompt-colon,entry,case-indicator ]; +} + +#textbox-prompt-colon { + expand: false; + str: ":"; + margin: 0px 0.3em 0em 0em; + text-color: @normal-foreground; +} + +#listview { + fixed-height: 0; + border: 2px 0px 0px; + border-color: @border-color; + spacing: 2px; + scrollbar: true; + padding: 2px 0px 0px; +} + +#element { + border: 0; + padding: 1px; +} + +#element.normal.normal { + background-color: @normal-background; + text-color: @normal-foreground; +} + +#element.normal.urgent { + background-color: @urgent-background; + text-color: @urgent-foreground; +} + +#element.normal.active { + background-color: @active-background; + text-color: @active-foreground; +} + +#element.selected.normal { + background-color: @selected-normal-background; + text-color: @selected-normal-foreground; +} + +#element.selected.urgent { + background-color: @selected-urgent-background; + text-color: @selected-urgent-foreground; +} + +#element.selected.active { + background-color: @selected-active-background; + text-color: @selected-active-foreground; +} + +#element.alternate.normal { + background-color: @alternate-normal-background; + text-color: @alternate-normal-foreground; +} + +#element.alternate.urgent { + background-color: @alternate-urgent-background; + text-color: @alternate-urgent-foreground; +} + +#element.alternate.active { + background-color: @alternate-active-background; + text-color: @alternate-active-foreground; +} + +#scrollbar { + width: 4px; + border: 0; + handle-width: 8px; + padding: 0; +} + +#sidebar { + border: 2px 0px 0px; + border-color: @border-color; +} + +#button { + text-color: @normal-foreground; +} + +#button.selected { + background-color: @selected-normal-background; + text-color: @selected-normal-foreground; +} + +#inputbar { + spacing: 0; + text-color: @normal-foreground; + padding: 1px; +} + +#case-indicator { + spacing: 0; + text-color: @normal-foreground; +} + +#entry { + spacing: 0; + text-color: @normal-foreground; +} + +#prompt { + spacing: 0; + text-color: @normal-foreground; +} diff --git a/.cache/wal/colors-rofi-light.rasi b/.cache/wal/colors-rofi-light.rasi new file mode 100644 index 0000000..8e67236 --- /dev/null +++ b/.cache/wal/colors-rofi-light.rasi @@ -0,0 +1,161 @@ +* { + active-background: #3c2a27; + active-foreground: @foreground; + normal-background: @background; + normal-foreground: @foreground; + urgent-background: #292b37; + urgent-foreground: @foreground; + + alternate-active-background: @background; + alternate-active-foreground: @foreground; + alternate-normal-background: @background; + alternate-normal-foreground: @foreground; + alternate-urgent-background: @background; + alternate-urgent-foreground: @foreground; + + selected-active-background: #292b37; + selected-active-foreground: @foreground; + selected-normal-background: #3c2a27; + selected-normal-foreground: @foreground; + selected-urgent-background: #3b3033; + selected-urgent-foreground: @foreground; + + background-color: @background; + background: #c4c4c4; + foreground: #141415; + border-color: @background; + spacing: 2; +} + +#window { + background-color: @background; + border: 0; + padding: 2.5ch; +} + +#mainbox { + border: 0; + padding: 0; +} + +#message { + border: 2px 0px 0px; + border-color: @border-color; + padding: 1px; +} + +#textbox { + text-color: @foreground; +} + +#inputbar { + children: [ prompt,textbox-prompt-colon,entry,case-indicator ]; +} + +#textbox-prompt-colon { + expand: false; + str: ":"; + margin: 0px 0.3em 0em 0em; + text-color: @normal-foreground; +} + +#listview { + fixed-height: 0; + border: 2px 0px 0px; + border-color: @border-color; + spacing: 2px; + scrollbar: true; + padding: 2px 0px 0px; +} + +#element { + border: 0; + padding: 1px; +} + +#element.normal.normal { + background-color: @normal-background; + text-color: @normal-foreground; +} + +#element.normal.urgent { + background-color: @urgent-background; + text-color: @urgent-foreground; +} + +#element.normal.active { + background-color: @active-background; + text-color: @active-foreground; +} + +#element.selected.normal { + background-color: @selected-normal-background; + text-color: @selected-normal-foreground; +} + +#element.selected.urgent { + background-color: @selected-urgent-background; + text-color: @selected-urgent-foreground; +} + +#element.selected.active { + background-color: @selected-active-background; + text-color: @selected-active-foreground; +} + +#element.alternate.normal { + background-color: @alternate-normal-background; + text-color: @alternate-normal-foreground; +} + +#element.alternate.urgent { + background-color: @alternate-urgent-background; + text-color: @alternate-urgent-foreground; +} + +#element.alternate.active { + background-color: @alternate-active-background; + text-color: @alternate-active-foreground; +} + +#scrollbar { + width: 4px; + border: 0; + handle-width: 8px; + padding: 0; +} + +#sidebar { + border: 2px 0px 0px; + border-color: @border-color; +} + +#button { + text-color: @normal-foreground; +} + +#button.selected { + background-color: @selected-normal-background; + text-color: @selected-normal-foreground; +} + +#inputbar { + spacing: 0; + text-color: @normal-foreground; + padding: 1px; +} + +#case-indicator { + spacing: 0; + text-color: @normal-foreground; +} + +#entry { + spacing: 0; + text-color: @normal-foreground; +} + +#prompt { + spacing: 0; + text-color: @normal-foreground; +} diff --git a/.cache/wal/colors-speedcrunch.json b/.cache/wal/colors-speedcrunch.json new file mode 100644 index 0000000..ead54c0 --- /dev/null +++ b/.cache/wal/colors-speedcrunch.json @@ -0,0 +1,15 @@ +{ + "cursor": "#c4c4c4", + "number": "#c4c4c4", + "parens": "#4b4240", + "result": "#3f3330", + "comment": "#4e4e4f", + "matched": "#3f3330", + "function": "#292b37", + "operator": "#3b3033", + "variable": "#3c2a27", + "scrollbar": "#3b3033", + "separator": "#3b3033", + "background": "#141415", + "editorbackground": "#141415" +} diff --git a/.cache/wal/colors-sway b/.cache/wal/colors-sway new file mode 100644 index 0000000..09f95c7 --- /dev/null +++ b/.cache/wal/colors-sway @@ -0,0 +1,21 @@ +set $wallpaper /home/loek/.config/wpg/wallpapers/pywal.png + +set $background #141415 +set $foreground #c4c4c4 + +set $color0 #141415 +set $color1 #292b37 +set $color2 #3c2a27 +set $color3 #3b3033 +set $color4 #3f3330 +set $color5 #4b4240 +set $color6 #66615a +set $color7 #c4c4c4 +set $color8 #4e4e4f +set $color9 #292b37 +set $color10 #3c2a27 +set $color11 #3b3033 +set $color12 #3f3330 +set $color13 #4b4240 +set $color14 #66615a +set $color15 #c4c4c4 diff --git a/.cache/wal/colors-tty.sh b/.cache/wal/colors-tty.sh new file mode 100644 index 0000000..3e7acdb --- /dev/null +++ b/.cache/wal/colors-tty.sh @@ -0,0 +1,19 @@ +#!/bin/sh +[ "${TERM:-none}" = "linux" ] && \ + printf '%b' '\e]P0141415 + \e]P1292b37 + \e]P23c2a27 + \e]P33b3033 + \e]P43f3330 + \e]P54b4240 + \e]P666615a + \e]P7c4c4c4 + \e]P84e4e4f + \e]P9292b37 + \e]PA3c2a27 + \e]PB3b3033 + \e]PC3f3330 + \e]PD4b4240 + \e]PE66615a + \e]PFc4c4c4 + \ec' diff --git a/.cache/wal/colors-wal-dmenu.h b/.cache/wal/colors-wal-dmenu.h new file mode 100644 index 0000000..43ddfac --- /dev/null +++ b/.cache/wal/colors-wal-dmenu.h @@ -0,0 +1,6 @@ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#c4c4c4", "#141415" }, + [SchemeSel] = { "#c4c4c4", "#292b37" }, + [SchemeOut] = { "#c4c4c4", "#66615a" }, +}; diff --git a/.cache/wal/colors-wal-dwm.h b/.cache/wal/colors-wal-dwm.h new file mode 100644 index 0000000..2380319 --- /dev/null +++ b/.cache/wal/colors-wal-dwm.h @@ -0,0 +1,18 @@ +static const char norm_fg[] = "#c4c4c4"; +static const char norm_bg[] = "#141415"; +static const char norm_border[] = "#4e4e4f"; + +static const char sel_fg[] = "#c4c4c4"; +static const char sel_bg[] = "#3c2a27"; +static const char sel_border[] = "#c4c4c4"; + +static const char urg_fg[] = "#c4c4c4"; +static const char urg_bg[] = "#292b37"; +static const char urg_border[] = "#292b37"; + +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { norm_fg, norm_bg, norm_border }, // unfocused wins + [SchemeSel] = { sel_fg, sel_bg, sel_border }, // the focused win + [SchemeUrg] = { urg_fg, urg_bg, urg_border }, +}; diff --git a/.cache/wal/colors-wal-st.h b/.cache/wal/colors-wal-st.h new file mode 100644 index 0000000..55463ed --- /dev/null +++ b/.cache/wal/colors-wal-st.h @@ -0,0 +1,34 @@ +const char *colorname[] = { + + /* 8 normal colors */ + [0] = "#141415", /* black */ + [1] = "#292b37", /* red */ + [2] = "#3c2a27", /* green */ + [3] = "#3b3033", /* yellow */ + [4] = "#3f3330", /* blue */ + [5] = "#4b4240", /* magenta */ + [6] = "#66615a", /* cyan */ + [7] = "#c4c4c4", /* white */ + + /* 8 bright colors */ + [8] = "#4e4e4f", /* black */ + [9] = "#292b37", /* red */ + [10] = "#3c2a27", /* green */ + [11] = "#3b3033", /* yellow */ + [12] = "#3f3330", /* blue */ + [13] = "#4b4240", /* magenta */ + [14] = "#66615a", /* cyan */ + [15] = "#c4c4c4", /* white */ + + /* special colors */ + [256] = "#141415", /* background */ + [257] = "#c4c4c4", /* foreground */ + [258] = "#c4c4c4", /* cursor */ +}; + +/* Default colors (colorname index) + * foreground, background, cursor */ + unsigned int defaultbg = 0; + unsigned int defaultfg = 257; + unsigned int defaultcs = 258; + unsigned int defaultrcs= 258; diff --git a/.cache/wal/colors-wal-tabbed.h b/.cache/wal/colors-wal-tabbed.h new file mode 100644 index 0000000..01d5d13 --- /dev/null +++ b/.cache/wal/colors-wal-tabbed.h @@ -0,0 +1,6 @@ +static const char* selbgcolor = "#141415"; +static const char* selfgcolor = "#c4c4c4"; +static const char* normbgcolor = "#3c2a27"; +static const char* normfgcolor = "#c4c4c4"; +static const char* urgbgcolor = "#292b37"; +static const char* urgfgcolor = "#c4c4c4"; diff --git a/.cache/wal/colors-wal.vim b/.cache/wal/colors-wal.vim new file mode 100644 index 0000000..a23e0f7 --- /dev/null +++ b/.cache/wal/colors-wal.vim @@ -0,0 +1,23 @@ +" Special +let wallpaper = "/home/loek/.config/wpg/wallpapers/pywal.png" +let background = "#141415" +let foreground = "#c4c4c4" +let cursor = "#c4c4c4" + +" Colors +let color0 = "#141415" +let color1 = "#292b37" +let color2 = "#3c2a27" +let color3 = "#3b3033" +let color4 = "#3f3330" +let color5 = "#4b4240" +let color6 = "#66615a" +let color7 = "#c4c4c4" +let color8 = "#4e4e4f" +let color9 = "#292b37" +let color10 = "#3c2a27" +let color11 = "#3b3033" +let color12 = "#3f3330" +let color13 = "#4b4240" +let color14 = "#66615a" +let color15 = "#c4c4c4" diff --git a/.cache/wal/colors-waybar.css b/.cache/wal/colors-waybar.css new file mode 100644 index 0000000..ede28aa --- /dev/null +++ b/.cache/wal/colors-waybar.css @@ -0,0 +1,20 @@ +@define-color foreground #c4c4c4; +@define-color background #141415; +@define-color cursor #c4c4c4; + +@define-color color0 #141415; +@define-color color1 #292b37; +@define-color color2 #3c2a27; +@define-color color3 #3b3033; +@define-color color4 #3f3330; +@define-color color5 #4b4240; +@define-color color6 #66615a; +@define-color color7 #c4c4c4; +@define-color color8 #4e4e4f; +@define-color color9 #292b37; +@define-color color10 #3c2a27; +@define-color color11 #3b3033; +@define-color color12 #3f3330; +@define-color color13 #4b4240; +@define-color color14 #66615a; +@define-color color15 #c4c4c4; diff --git a/.cache/wal/colors.Xresources b/.cache/wal/colors.Xresources new file mode 100644 index 0000000..cebd8a2 --- /dev/null +++ b/.cache/wal/colors.Xresources @@ -0,0 +1,68 @@ +! X colors. +! Generated by 'wal' +*foreground: #c4c4c4 +*background: #141415 +*.foreground: #c4c4c4 +*.background: #141415 +emacs*foreground: #c4c4c4 +emacs*background: #141415 +URxvt*foreground: #c4c4c4 +XTerm*foreground: #c4c4c4 +UXTerm*foreground: #c4c4c4 +URxvt*background: [100]#141415 +XTerm*background: #141415 +UXTerm*background: #141415 +URxvt*cursorColor: #c4c4c4 +XTerm*cursorColor: #c4c4c4 +UXTerm*cursorColor: #c4c4c4 +URxvt*borderColor: [100]#141415 + +! Colors 0-15. +*.color0: #141415 +*color0: #141415 +*.color1: #292b37 +*color1: #292b37 +*.color2: #3c2a27 +*color2: #3c2a27 +*.color3: #3b3033 +*color3: #3b3033 +*.color4: #3f3330 +*color4: #3f3330 +*.color5: #4b4240 +*color5: #4b4240 +*.color6: #66615a +*color6: #66615a +*.color7: #c4c4c4 +*color7: #c4c4c4 +*.color8: #4e4e4f +*color8: #4e4e4f +*.color9: #292b37 +*color9: #292b37 +*.color10: #3c2a27 +*color10: #3c2a27 +*.color11: #3b3033 +*color11: #3b3033 +*.color12: #3f3330 +*color12: #3f3330 +*.color13: #4b4240 +*color13: #4b4240 +*.color14: #66615a +*color14: #66615a +*.color15: #c4c4c4 +*color15: #c4c4c4 + +! Black color that will not be affected by bold highlighting. +*.color66: #141415 +*color66: #141415 + +! Xclock colors. +XClock*foreground: #c4c4c4 +XClock*background: #141415 +XClock*majorColor: rgba:c4/c4/c4/ff +XClock*minorColor: rgba:c4/c4/c4/ff +XClock*hourColor: rgba:c4/c4/c4/ff +XClock*minuteColor: rgba:c4/c4/c4/ff +XClock*secondColor: rgba:c4/c4/c4/ff + +! Set depth to make transparency work. +URxvt*depth: 32 diff --git a/.cache/wal/colors.css b/.cache/wal/colors.css new file mode 100644 index 0000000..f4d80a0 --- /dev/null +++ b/.cache/wal/colors.css @@ -0,0 +1,28 @@ +/* CSS variables + Generated by 'wal' */ +:root { + --wallpaper: url("/home/loek/.config/wpg/wallpapers/pywal.png"); + + /* Special */ + --background: #141415; + --foreground: #c4c4c4; + --cursor: #c4c4c4; + + /* Colors */ + --color0: #141415; + --color1: #292b37; + --color2: #3c2a27; + --color3: #3b3033; + --color4: #3f3330; + --color5: #4b4240; + --color6: #66615a; + --color7: #c4c4c4; + --color8: #4e4e4f; + --color9: #292b37; + --color10: #3c2a27; + --color11: #3b3033; + --color12: #3f3330; + --color13: #4b4240; + --color14: #66615a; + --color15: #c4c4c4; +} diff --git a/.cache/wal/colors.hs b/.cache/wal/colors.hs new file mode 100644 index 0000000..3265973 --- /dev/null +++ b/.cache/wal/colors.hs @@ -0,0 +1,37 @@ +--Place this file in your .xmonad/lib directory and import module Colors into .xmonad/xmonad.hs config +--The easy way is to create a soft link from this file to the file in .xmonad/lib using ln -s +--Then recompile and restart xmonad. + +module Colors + ( wallpaper + , background, foreground, cursor + , color0, color1, color2, color3, color4, color5, color6, color7 + , color8, color9, color10, color11, color12, color13, color14, color15 + ) where + +-- Shell variables +-- Generated by 'wal' +wallpaper="/home/loek/.config/wpg/wallpapers/pywal.png" + +-- Special +background="#141415" +foreground="#c4c4c4" +cursor="#c4c4c4" + +-- Colors +color0="#141415" +color1="#292b37" +color2="#3c2a27" +color3="#3b3033" +color4="#3f3330" +color5="#4b4240" +color6="#66615a" +color7="#c4c4c4" +color8="#4e4e4f" +color9="#292b37" +color10="#3c2a27" +color11="#3b3033" +color12="#3f3330" +color13="#4b4240" +color14="#66615a" +color15="#c4c4c4" diff --git a/.cache/wal/colors.json b/.cache/wal/colors.json new file mode 100644 index 0000000..9148f6c --- /dev/null +++ b/.cache/wal/colors.json @@ -0,0 +1,28 @@ +{ + "wallpaper": "/home/loek/.config/wpg/wallpapers/pywal.png", + "alpha": "100", + + "special": { + "background": "#141415", + "foreground": "#c4c4c4", + "cursor": "#c4c4c4" + }, + "colors": { + "color0": "#141415", + "color1": "#292b37", + "color2": "#3c2a27", + "color3": "#3b3033", + "color4": "#3f3330", + "color5": "#4b4240", + "color6": "#66615a", + "color7": "#c4c4c4", + "color8": "#4e4e4f", + "color9": "#292b37", + "color10": "#3c2a27", + "color11": "#3b3033", + "color12": "#3f3330", + "color13": "#4b4240", + "color14": "#66615a", + "color15": "#c4c4c4" + } +} diff --git a/.cache/wal/colors.scss b/.cache/wal/colors.scss new file mode 100644 index 0000000..04d3d4d --- /dev/null +++ b/.cache/wal/colors.scss @@ -0,0 +1,26 @@ +// SCSS Variables +// Generated by 'wal' +$wallpaper: "/home/loek/.config/wpg/wallpapers/pywal.png"; + +// Special +$background: #141415; +$foreground: #c4c4c4; +$cursor: #c4c4c4; + +// Colors +$color0: #141415; +$color1: #292b37; +$color2: #3c2a27; +$color3: #3b3033; +$color4: #3f3330; +$color5: #4b4240; +$color6: #66615a; +$color7: #c4c4c4; +$color8: #4e4e4f; +$color9: #292b37; +$color10: #3c2a27; +$color11: #3b3033; +$color12: #3f3330; +$color13: #4b4240; +$color14: #66615a; +$color15: #c4c4c4; diff --git a/.cache/wal/colors.sh b/.cache/wal/colors.sh new file mode 100644 index 0000000..30ebe31 --- /dev/null +++ b/.cache/wal/colors.sh @@ -0,0 +1,36 @@ +# Shell variables +# Generated by 'wal' +wallpaper='/home/loek/.config/wpg/wallpapers/pywal.png' + +# Special +background='#141415' +foreground='#c4c4c4' +cursor='#c4c4c4' + +# Colors +color0='#141415' +color1='#292b37' +color2='#3c2a27' +color3='#3b3033' +color4='#3f3330' +color5='#4b4240' +color6='#66615a' +color7='#c4c4c4' +color8='#4e4e4f' +color9='#292b37' +color10='#3c2a27' +color11='#3b3033' +color12='#3f3330' +color13='#4b4240' +color14='#66615a' +color15='#c4c4c4' + +# FZF colors +export FZF_DEFAULT_OPTS=" + $FZF_DEFAULT_OPTS + --color fg:7,bg:0,hl:1,fg+:232,bg+:1,hl+:255 + --color info:7,prompt:2,spinner:1,pointer:232,marker:1 +" + +# Fix LS_COLORS being unreadable. +export LS_COLORS="${LS_COLORS}:su=30;41:ow=30;42:st=30;44:" diff --git a/.cache/wal/colors.yml b/.cache/wal/colors.yml new file mode 100644 index 0000000..4552903 --- /dev/null +++ b/.cache/wal/colors.yml @@ -0,0 +1,24 @@ +wallpaper: "/home/loek/.config/wpg/wallpapers/pywal.png" + +special: + background: "#141415" + foreground: "#c4c4c4" + cursor: "#c4c4c4" + +colors: + color0: "#141415" + color1: "#292b37" + color2: "#3c2a27" + color3: "#3b3033" + color4: "#3f3330" + color5: "#4b4240" + color6: "#66615a" + color7: "#c4c4c4" + color8: "#4e4e4f" + color9: "#292b37" + color10: "#3c2a27" + color11: "#3b3033" + color12: "#3f3330" + color13: "#4b4240" + color14: "#66615a" + color15: "#c4c4c4" diff --git a/.cache/wal/sequences b/.cache/wal/sequences new file mode 100644 index 0000000..5094427 --- /dev/null +++ b/.cache/wal/sequences @@ -0,0 +1 @@ +]4;0;#141415\]4;1;#292b37\]4;2;#3c2a27\]4;3;#3b3033\]4;4;#3f3330\]4;5;#4b4240\]4;6;#66615a\]4;7;#c4c4c4\]4;8;#4e4e4f\]4;9;#292b37\]4;10;#3c2a27\]4;11;#3b3033\]4;12;#3f3330\]4;13;#4b4240\]4;14;#66615a\]4;15;#c4c4c4\]10;#c4c4c4\]11;#141415\]12;#c4c4c4\]13;#c4c4c4\]17;#c4c4c4\]19;#141415\]4;232;#141415\]4;256;#c4c4c4\]708;#141415\ \ No newline at end of file diff --git a/.cache/wal/wal b/.cache/wal/wal new file mode 100644 index 0000000..30e642d --- /dev/null +++ b/.cache/wal/wal @@ -0,0 +1 @@ +/home/loek/.config/wpg/wallpapers/pywal.png \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/BDFDB.config.json b/.config/BetterDiscord/plugins/BDFDB.config.json new file mode 100644 index 0000000..7b5a5ac --- /dev/null +++ b/.config/BetterDiscord/plugins/BDFDB.config.json @@ -0,0 +1,5 @@ +{ + "welcomeScreen": { + "seen": false + } +} \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/BadgesEverywhere.config.json b/.config/BetterDiscord/plugins/BadgesEverywhere.config.json new file mode 100644 index 0000000..404c91c --- /dev/null +++ b/.config/BetterDiscord/plugins/BadgesEverywhere.config.json @@ -0,0 +1,29 @@ +{ + "badges": { + "1": true, + "2": true, + "4": true, + "8": true, + "64": true, + "128": true, + "256": true, + "512": true, + "16384": true, + "131072": true, + "262144": true, + "524288": true + }, + "changelog": { + "currentversion": "1.5.5" + }, + "indicators": { + "CURRENT_GUILD_BOOST": true + }, + "settings": { + "showInChat": true, + "showInMemberList": true, + "showInPopout": true, + "showNitroDate": true, + "useColoredVersion": true + } +} \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/BetterSearchPage.config.json b/.config/BetterDiscord/plugins/BetterSearchPage.config.json new file mode 100644 index 0000000..856dabf --- /dev/null +++ b/.config/BetterDiscord/plugins/BetterSearchPage.config.json @@ -0,0 +1,7 @@ +{ + "settings": { + "addFirstLast": true, + "addJumpTo": true, + "cloneToTheTop": true + } +} \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/BetterSearchPage.plugin.js b/.config/BetterDiscord/plugins/BetterSearchPage.plugin.js new file mode 100644 index 0000000..64b71e4 --- /dev/null +++ b/.config/BetterDiscord/plugins/BetterSearchPage.plugin.js @@ -0,0 +1,194 @@ +//META{"name":"BetterSearchPage","authorId":"278543574059057154","invite":"Jx3TjNS","donate":"https://www.paypal.me/MircoWittrien","patreon":"https://www.patreon.com/MircoWittrien","website":"https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/BetterSearchPage","source":"https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/BetterSearchPage/BetterSearchPage.plugin.js"}*// + +var BetterSearchPage = (_ => { + return class BetterSearchPage { + getName () {return "BetterSearchPage";} + + getVersion () {return "1.1.5";} + + getAuthor () {return "DevilBro";} + + getDescription () {return "Adds some extra controls to the search results page.";} + + constructor () { + this.patchedModules = { + after: { + SearchResultsInner: "default" + } + }; + } + + initConstructor () { + this.defaults = { + settings: { + addFirstLast: {value:true, description:"Adds a first and last page button."}, + addJumpTo: {value:true, description:"Adds a jump to input field (press enter to jump)."}, + cloneToTheTop: {value:true, description:"Clones the controls to the top of the results page."} + } + }; + } + + getSettingsPanel () { + if (!window.BDFDB || typeof BDFDB != "object" || !BDFDB.loaded || !this.started) return; + let settings = BDFDB.DataUtils.get(this, "settings"); + let settingsPanel, settingsItems = []; + + for (let key in settings) settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + className: BDFDB.disCN.marginbottom8, + type: "Switch", + plugin: this, + keys: ["settings", key], + label: this.defaults.settings[key].description, + value: settings[key] + })); + + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, settingsItems); + } + + // Legacy + load () {} + + start () { + if (!window.BDFDB) window.BDFDB = {myPlugins:{}}; + if (window.BDFDB && window.BDFDB.myPlugins && typeof window.BDFDB.myPlugins == "object") window.BDFDB.myPlugins[this.getName()] = this; + let libraryScript = document.querySelector("head script#BDFDBLibraryScript"); + if (!libraryScript || (performance.now() - libraryScript.getAttribute("date")) > 600000) { + if (libraryScript) libraryScript.remove(); + libraryScript = document.createElement("script"); + libraryScript.setAttribute("id", "BDFDBLibraryScript"); + libraryScript.setAttribute("type", "text/javascript"); + libraryScript.setAttribute("src", "https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.min.js"); + libraryScript.setAttribute("date", performance.now()); + libraryScript.addEventListener("load", _ => {this.initialize();}); + document.head.appendChild(libraryScript); + } + else if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) this.initialize(); + this.startTimeout = setTimeout(_ => { + try {return this.initialize();} + catch (err) {console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not initiate plugin! " + err);} + }, 30000); + } + + initialize () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + if (this.started) return; + BDFDB.PluginUtils.init(this); + + BDFDB.ModuleUtils.forceAllUpdates(this); + } + else console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not load BD functions!"); + } + + stop () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + this.stopping = true; + + BDFDB.ModuleUtils.forceAllUpdates(this); + + BDFDB.PluginUtils.clear(this); + } + } + + + // Begin of own functions + + onSettingsClosed (e) { + if (this.SettingsUpdated) { + delete this.SettingsUpdated; + BDFDB.ModuleUtils.forceAllUpdates(this); + } + } + + processSearchResultsInner (e) { + if (e.instance.props.search) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {name:"SearchPagination"}); + if (index > -1) { + let settings = BDFDB.DataUtils.get(this, "settings"); + let currentpage = parseInt(Math.floor(e.instance.props.search.offset / BDFDB.DiscordConstants.SEARCH_PAGE_SIZE)) + 1; + let maxpage = e.instance.props.search.totalResults > 5000 ? parseInt(Math.ceil(5000 / BDFDB.DiscordConstants.SEARCH_PAGE_SIZE)) : parseInt(Math.ceil(e.instance.props.search.totalResults / BDFDB.DiscordConstants.SEARCH_PAGE_SIZE)); + let doJump = page => { + page = page < 1 ? 1 : (page > maxpage ? maxpage : page); + if (page < currentpage) BDFDB.LibraryModules.SearchPageUtils.searchPreviousPage(e.instance.props.searchId, (currentpage - page) * BDFDB.DiscordConstants.SEARCH_PAGE_SIZE); + else if (page > currentpage) BDFDB.LibraryModules.SearchPageUtils.searchNextPage(e.instance.props.searchId, (page - currentpage) * BDFDB.DiscordConstants.SEARCH_PAGE_SIZE); + }; + let pagination = children[index].type(children[index].props); + if (!pagination) return; + + if (currentpage >= maxpage) { + pagination.props.children[2].props.className = BDFDB.DOMUtils.formatClassName(pagination.props.children[2].props.className, BDFDB.disCN.searchresultspaginationdisabled); + pagination.props.children[2].props.onClick = _ => {}; + } + pagination.props.children[0] = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: "Previous", + children: pagination.props.children[0] + }); + pagination.props.children[2] = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: currentpage >= maxpage ? "Max Page is 200" : "Next", + tooltipConfig: {color: currentpage >= maxpage && BDFDB.LibraryComponents.TooltipContainer.Colors.RED}, + children: pagination.props.children[2] + }); + if (settings.addFirstLast) { + pagination.props.children.unshift(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: "First", + "aria-label": "First", + onClick: _ => {if (currentpage != 1) doJump(1);}, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.searchresultspaginationbutton, currentpage == 1 && BDFDB.disCN.searchresultspaginationdisabled), + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.searchresultspaginationicon, + nativeClass: true, + iconSVG: `` + }) + }) + })); + pagination.props.children.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: currentpage >= maxpage ? "Max Page is 200" : "Last", + tooltipConfig: {color: currentpage >= maxpage && BDFDB.LibraryComponents.TooltipContainer.Colors.RED}, + "aria-label": "Last", + onClick: _ => {if (currentpage != maxpage) doJump(maxpage);}, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.searchresultspaginationbutton, currentpage >= maxpage && BDFDB.disCN.searchresultspaginationdisabled), + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.searchresultspaginationicon, + nativeClass: true, + iconSVG: `` + }) + }) + })); + } + if (settings.addJumpTo) { + pagination.props.children.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + key: "BSP-pagination-jumpinput", + type: "number", + size: BDFDB.LibraryComponents.TextInput.Sizes.MINI, + suppress: true, + value: currentpage, + min: 1, + max: maxpage, + onKeyDown: (e, inputinstance) => {if (e.which == 13) doJump(inputinstance.props.value);} + })); + pagination.props.children.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: BDFDB.LanguageUtils.LanguageStrings.JUMP, + "aria-label": BDFDB.LanguageUtils.LanguageStrings.JUMP, + onClick: (e, buttoninstance) => { + let jumpinput = BDFDB.ReactUtils.findOwner(buttoninstance._reactInternalFiber.return, {key:"BSP-pagination-jumpinput"}); + if (jumpinput) doJump(jumpinput.props.value); + }, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + className: BDFDB.disCN.searchresultspaginationbutton, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN.searchresultspaginationicon, + nativeClass: true, + style: {transform: "rotate(90deg"}, + name: BDFDB.LibraryComponents.SvgIcon.Names.RIGHT_CARET + }) + }) + })); + } + children[index] = pagination; + if (settings.cloneToTheTop) children.unshift(pagination); + } + } + } + } +})(); \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/EditChannels.config.json b/.config/BetterDiscord/plugins/EditChannels.config.json new file mode 100644 index 0000000..ac031cf --- /dev/null +++ b/.config/BetterDiscord/plugins/EditChannels.config.json @@ -0,0 +1,17 @@ +{ + "changelog": { + "currentversion": "4.1.3" + }, + "settings": { + "changeChannelIcon": true, + "changeInAuditLog": true, + "changeInAutoComplete": true, + "changeInChannelHeader": true, + "changeInChannelList": true, + "changeInChatTextarea": true, + "changeInInviteLog": true, + "changeInMentions": true, + "changeInQuickSwitcher": true, + "changeInRecentMentions": true + } +} \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/EditChannels.plugin.js b/.config/BetterDiscord/plugins/EditChannels.plugin.js new file mode 100644 index 0000000..955776e --- /dev/null +++ b/.config/BetterDiscord/plugins/EditChannels.plugin.js @@ -0,0 +1,773 @@ +//META{"name":"EditChannels","authorId":"278543574059057154","invite":"Jx3TjNS","donate":"https://www.paypal.me/MircoWittrien","patreon":"https://www.patreon.com/MircoWittrien","website":"https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/EditChannels","source":"https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/EditChannels/EditChannels.plugin.js"}*// + +var EditChannels = (_ => { + return class EditChannels { + getName () {return "EditChannels";} + + getVersion () {return "4.1.3";} + + getAuthor () {return "DevilBro";} + + getDescription () {return "Allows you to rename and recolor channelnames.";} + + constructor () { + this.changelog = { + "fixed":[["Context Menu Update","Fixes for the context menu update, yaaaaaay"]] + }; + + this.patchedModules = { + before: { + ChannelEditorContainer: "render", + ChannelAutoComplete: "render", + AutocompleteChannelResult: "render", + AuditLog: "render", + SettingsInvites: "render", + HeaderBarContainer: "render", + ChannelCategoryItem: "render", + ChannelItem: "render", + QuickSwitchChannelResult: "render", + MessageContent: "type" + }, + after: { + AutocompleteChannelResult: "render", + AuditLog: "render", + HeaderBarContainer: "render", + ChannelCategoryItem: "render", + ChannelItem: "render", + QuickSwitchChannelResult: "render", + MessagesPopout: "render" + } + }; + } + + initConstructor () { + this.css = ` + ${BDFDB.dotCN.messagespopoutchannelname}:hover > span[style*="color"] { + text-decoration: underline; + } + ${BDFDB.dotCN.categorywrapper}:hover ${BDFDB.dotCN.categoryname} span[style*="color"], + ${BDFDB.dotCN.categorywrapper}:hover ${BDFDB.dotCN.categoryicon}.EC-changed, + ${BDFDB.dotCN.channelwrapper + BDFDB.notCN.channelmodeselected + BDFDB.notCN.channelmodeconnected}:hover ${BDFDB.dotCN.channelname} span[style*="color"], + ${BDFDB.dotCN.channelwrapper + BDFDB.notCN.channelmodeselected + BDFDB.notCN.channelmodeconnected}:hover ${BDFDB.dotCN.channelicon}.EC-changed { + filter: brightness(150%); + } + `; + + this.defaults = { + settings: { + changeChannelIcon: {value:true, inner:false, description:"Change color of Channel Icon"}, + changeInChatTextarea: {value:true, inner:true, description:"Chat Textarea"}, + changeInMentions: {value:true, inner:true, description:"Mentions"}, + changeInChannelList: {value:true, inner:true, description:"Channel List"}, + changeInChannelHeader: {value:true, inner:true, description:"Channel Header"}, + changeInRecentMentions: {value:true, inner:true, description:"Recent Mentions Popout"}, + changeInAutoComplete: {value:true, inner:true, description:"Autocomplete Menu"}, + changeInAuditLog: {value:true, inner:true, description:"Audit Log"}, + changeInInviteLog: {value:true, inner:true, description:"Invite Log"}, + changeInQuickSwitcher: {value:true, inner:true, description:"Quick Switcher"} + } + }; + } + + getSettingsPanel () { + if (!window.BDFDB || typeof BDFDB != "object" || !BDFDB.loaded || !this.started) return; + let settings = BDFDB.DataUtils.get(this, "settings"); + let settingsPanel, settingsItems = [], innerItems = []; + + for (let key in settings) (!this.defaults.settings[key].inner ? settingsItems : innerItems).push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + className: BDFDB.disCN.marginbottom8, + type: "Switch", + plugin: this, + keys: ["settings", key], + label: this.defaults.settings[key].description, + value: settings[key] + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelInner, { + title: "Change Channels in:", + first: settingsItems.length == 0, + children: innerItems + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Button", + className: BDFDB.disCN.marginbottom8, + color: BDFDB.LibraryComponents.Button.Colors.RED, + label: "Reset all Channels", + onClick: _ => { + BDFDB.ModalUtils.confirm(this, "Are you sure you want to reset all channels?", _ => { + BDFDB.DataUtils.remove(this, "channels"); + this.forceUpdateAll(); + }); + }, + children: BDFDB.LanguageUtils.LanguageStrings.RESET + })); + + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, settingsItems); + } + + // Legacy + load () {} + + start () { + if (!window.BDFDB) window.BDFDB = {myPlugins:{}}; + if (window.BDFDB && window.BDFDB.myPlugins && typeof window.BDFDB.myPlugins == "object") window.BDFDB.myPlugins[this.getName()] = this; + let libraryScript = document.querySelector("head script#BDFDBLibraryScript"); + if (!libraryScript || (performance.now() - libraryScript.getAttribute("date")) > 600000) { + if (libraryScript) libraryScript.remove(); + libraryScript = document.createElement("script"); + libraryScript.setAttribute("id", "BDFDBLibraryScript"); + libraryScript.setAttribute("type", "text/javascript"); + libraryScript.setAttribute("src", "https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.min.js"); + libraryScript.setAttribute("date", performance.now()); + libraryScript.addEventListener("load", _ => {this.initialize();}); + document.head.appendChild(libraryScript); + } + else if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) this.initialize(); + this.startTimeout = setTimeout(_ => { + try {return this.initialize();} + catch (err) {console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not initiate plugin! " + err);} + }, 30000); + } + + initialize () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + if (this.started) return; + BDFDB.PluginUtils.init(this); + + let observer = new MutationObserver(_ => {this.changeAppTitle();}); + BDFDB.ObserverUtils.connect(this, document.head.querySelector("title"), {name:"appTitleObserver",instance:observer}, {childList:true}); + + this.forceUpdateAll(); + } + else console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not load BD functions!"); + } + + stop () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + this.stopping = true; + + let data = BDFDB.DataUtils.load(this, "channels"); + BDFDB.DataUtils.remove(this, "channels"); + try {this.forceUpdateAll();} catch (err) {} + BDFDB.DataUtils.save(data, this, "channels"); + + BDFDB.PluginUtils.clear(this); + } + } + + + // Begin of own functions + + onChannelContextMenu (e) { + if (e.instance.props.channel) { + let [children, index] = BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: "devmode-copy-id", group: true}); + children.splice(index > -1 ? index : children.length, 0, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels.context_localchannelsettings_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "settings-submenu"), + children: BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: [ + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels.submenu_channelsettings_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "settings-change"), + action: _ => { + BDFDB.ContextMenuUtils.close(e.instance); + this.openChannelSettingsModal(e.instance.props.channel); + } + }), + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels.submenu_resetsettings_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "settings-reset"), + disabled: !BDFDB.DataUtils.load(this, "channels", e.instance.props.channel.id), + action: _ => { + BDFDB.ContextMenuUtils.close(e.instance); + BDFDB.DataUtils.remove(this, "channels", e.instance.props.channel.id); + this.forceUpdateAll(); + } + }) + ] + }) + }) + })); + } + } + + onSettingsClosed () { + if (this.SettingsUpdated) { + delete this.SettingsUpdated; + this.forceUpdateAll(); + } + } + + processChannelEditorContainer (e) { + if (!e.instance.props.disabled && e.instance.props.channel && BDFDB.ChannelUtils.isTextChannel(e.instance.props.channel) && e.instance.props.type == BDFDB.DiscordConstants.TextareaTypes.NORMAL && BDFDB.DataUtils.get(this, "settings", "changeInChatTextarea")) { + let data = BDFDB.DataUtils.load(this, "channels", e.instance.props.channel.id); + e.instance.props.placeholder = BDFDB.LanguageUtils.LanguageStringsFormat("TEXTAREA_PLACEHOLDER", `#${data && data.name || e.instance.props.channel.name}`); + } + } + + processChannelAutoComplete (e) { + if (e.instance.state.autocompleteType == "CHANNELS" && BDFDB.ArrayUtils.is(e.instance.state.autocompletes.channels) && e.instance.props.channel && e.instance.props.channel.guild_id) { + let lastword = (e.instance.props.textValue || "").slice(1).toLowerCase(); + let channels = BDFDB.DataUtils.load(this, "channels"); + if (!channels || !lastword) return; + let channelarray = []; + for (let id in channels) if (channels[id] && channels[id].name) { + let channel = BDFDB.LibraryModules.ChannelStore.getChannel(id); + let category = channel && channel.parent_id && BDFDB.LibraryModules.ChannelStore.getChannel(channel.parent_id); + let catdata = category && channels[category.id] || {}; + if (BDFDB.ChannelUtils.isTextChannel(channel) && channel.guild_id == e.instance.props.channel.guild_id) channelarray.push(Object.assign({ + lowercasename: channels[id].name.toLowerCase(), + lowercasecatname: catdata && catdata.name && catdata.name.toLowerCase(), + channel, + category, + catdata + }, channels[id])); + } + channelarray = BDFDB.ArrayUtils.keySort(channelarray.filter(n => e.instance.state.autocompletes.channels.every(channel => channel.id != n.channel.id) && (n.lowercasename.indexOf(lastword) != -1 || (n.lowercasecatname && n.lowercasecatname.indexOf(lastword) != -1))), "lowercasename"); + e.instance.state.autocompletes.channels = [].concat(e.instance.state.autocompletes.channels, channelarray.map(n => n.channel)).slice(0, BDFDB.DiscordConstants.MAX_AUTOCOMPLETE_RESULTS); + } + } + + processAutocompleteChannelResult (e) { + if (e.instance.props.channel && BDFDB.DataUtils.get(this, "settings", "changeInAutoComplete")) { + if (!e.returnvalue) { + e.instance.props.channel = this.getChannelData(e.instance.props.channel.id); + if (e.instance.props.category) e.instance.props.category = this.getChannelData(e.instance.props.category.id); + } + else { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["className", BDFDB.disCN.marginleft4]]}); + if (index > -1) this.changeChannelColor(children[index], e.instance.props.channel.id); + [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["className", BDFDB.disCN.autocompleteicon]]}); + if (index > -1) this.changeChannelIconColor(children[index], e.instance.props.channel.id, {alpha: 0.6}); + if (e.instance.props.category) { + [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["className", BDFDB.disCN.autocompletedescription]]}); + if (index > -1) this.changeChannelColor(children[index], e.instance.props.category.id); + } + } + } + } + + processAuditLog (e) { + let channel = BDFDB.ReactUtils.getValue(e.instance, "props.log.options.channel"); + if (channel && BDFDB.DataUtils.get(this, "settings", "changeInAuditLog")) { + if (!e.returnvalue) e.instance.props.log.options.channel = this.getChannelData(channel.id); + else { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["children", [["#" + channel.name]]]]}); + if (index > -1) this.changeChannelColor(children[index], channel.id); + } + } + } + + processSettingsInvites (e) { + if (BDFDB.ObjectUtils.is(e.instance.props.invites) && BDFDB.DataUtils.get(this, "settings", "changeInInviteLog")) { + e.instance.props.invites = Object.assign({}, e.instance.props.invites); + for (let id in e.instance.props.invites) e.instance.props.invites[id] = new BDFDB.DiscordObjects.Invite(Object.assign({}, e.instance.props.invites[id], {channel: this.getChannelData(e.instance.props.invites[id].channel.id)})); + } + } + + processHeaderBarContainer (e) { + let channel = BDFDB.LibraryModules.ChannelStore.getChannel(e.instance.props.channelId); + if (channel && BDFDB.ChannelUtils.isTextChannel(channel) && BDFDB.DataUtils.get(this, "settings", "changeInChannelHeader")) { + if (!e.returnvalue) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.instance, {name: "Title"}); + if (index > -1) { + children[index].props.children = this.getChannelData(channel.id).name; + this.changeChannelColor(children[index], channel.id); + } + } + else { + let [children, index] = BDFDB.ReactUtils.findChildren(e.instance, {name: "Icon"}); + if (index > -1) { + let icon = BDFDB.ReactUtils.createElement(children[index].props.icon, { + className: BDFDB.disCN.channelheadericon + }); + this.changeChannelIconColor(icon, channel.id, {alpha: 0.6}); + children[index] = BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.channelheadericonwrapper, + children: icon + }) + } + } + } + } + + processChannelCategoryItem (e) { + if (e.instance.props.channel && BDFDB.DataUtils.get(this, "settings", "changeInChannelList")) { + if (!e.returnvalue) e.instance.props.channel = this.getChannelData(e.instance.props.channel.id); + else { + let modify = BDFDB.ObjectUtils.extract(e.instance.props, "muted", "locked", "selected", "unread", "connected"); + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["className", BDFDB.disCN.categoryname]]}); + if (index > -1) this.changeChannelColor(children[index], e.instance.props.channel.id, modify); + [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["className", BDFDB.disCN.categoryicon]]}); + if (index > -1) this.changeChannelIconColor(children[index], e.instance.props.channel.id, Object.assign({alpha: 0.6}, modify)); + } + } + } + + processChannelItem (e) { + if (e.instance.props.channel && BDFDB.DataUtils.get(this, "settings", "changeInChannelList")) { + if (!e.returnvalue) e.instance.props.channel = this.getChannelData(e.instance.props.channel.id); + else { + let modify = BDFDB.ObjectUtils.extract(e.instance.props, "muted", "locked", "selected", "unread", "connected"); + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["className", BDFDB.disCN.channelname]]}); + if (index > -1) this.changeChannelColor(children[index], e.instance.props.channel.id, modify); + [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["className", BDFDB.disCN.channelicon]]}); + if (index > -1) this.changeChannelIconColor(children[index], e.instance.props.channel.id, Object.assign({alpha: 0.6}, modify)); + } + } + } + + processQuickSwitchChannelResult (e) { + if (e.instance.props.channel && BDFDB.DataUtils.get(this, "settings", "changeInQuickSwitcher")) { + if (!e.returnvalue) { + e.instance.props.channel = this.getChannelData(e.instance.props.channel.id); + if (e.instance.props.category) e.instance.props.category = this.getChannelData(e.instance.props.category.id); + } + else { + let modify = BDFDB.ObjectUtils.extract(e.instance.props, "focused", "unread", "mentions"); + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["className", BDFDB.disCN.quickswitchresultmatch]]}); + if (index > -1) this.changeChannelColor(children[index], e.instance.props.channel.id, modify); + [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["className", BDFDB.disCN.quickswitchresulticon]]}); + if (index > -1) this.changeChannelIconColor(children[index], e.instance.props.channel.id, Object.assign({alpha: 0.6}, modify)); + if (e.instance.props.category) { + [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props:[["className", BDFDB.disCN.quickswitchresultnote]]}); + if (index > -1) this.changeChannelColor(children[index], e.instance.props.category.id); + } + } + } + } + + processMessagesPopout (e) { + if (BDFDB.DataUtils.get(this, "settings", "changeInRecentMentions")) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {name: "VerticalScroller"}); + if (index > -1 && children[index].props.children && BDFDB.ArrayUtils.is(children[index].props.children[0])) for (let i in children[index].props.children[0]) { + let divider = children[index].props.children[0][i]; + if (divider && divider.props && divider.props.className == BDFDB.disCN.messagespopoutchannelseparator) { + let channel = BDFDB.ReactUtils.findValue(children[index].props.children[0][parseInt(i)+1], "channel"); + if (BDFDB.ChannelUtils.isTextChannel(channel)) { + let [children2, index2] = BDFDB.ReactUtils.findChildren(divider, {props:[["className", BDFDB.disCN.messagespopoutchannelname]]}); + if (index2 > -1) { + children2[index2].props.children = "#" + this.getChannelData(channel.id).name; + this.changeChannelColor(children2[index2], channel.id); + } + } + } + } + } + } + + processMessageContent (e) { + if (BDFDB.ArrayUtils.is(e.instance.props.content) && BDFDB.DataUtils.get(this, "settings", "changeInMentions")) for (let ele of e.instance.props.content) { + if (BDFDB.ReactUtils.isValidElement(ele) && ele.type && ele.type.displayName == "Tooltip" && typeof ele.props.children == "function") { + let children = ele.props.children({}); + if (children && children.type.displayName == "Mention" && children.props.children && typeof children.props.children[0] == "string" && children.props.children[0][0] == "#") { + let channelName = children.props.children[0].slice(1); + let guildId = BDFDB.LibraryModules.LastGuildStore.getGuildId(); + let channels = guildId && (BDFDB.LibraryModules.GuildChannelStore.getChannels(guildId)[0] || BDFDB.LibraryModules.GuildChannelStore.getChannels(guildId).SELECTABLE); + if (Array.isArray(channels)) for (let channelObj of channels) { + if (channelName == channelObj.channel.name) { + let category = BDFDB.LibraryModules.ChannelStore.getChannel(channelObj.channel.parent_id); + if (!category || category && ele.props.text == category.name) { + if (category) { + let categoryData = BDFDB.DataUtils.load(this, "channels", category.id); + if (categoryData && categoryData.name) ele.props.text = categoryData.name; + } + let name = (BDFDB.DataUtils.load(this, "channels", channelObj.channel.id) || {}).name; + let color = this.getChannelDataColor(channelObj.channel.id); + if (name || color) { + let renderChildren = ele.props.children; + ele.props.children = (...args) => { + let renderedChildren = renderChildren(...args); + if (name) renderedChildren.props.children[0] = "#" + name; + if (color) { + let color1_0 = BDFDB.ColorUtils.convert(BDFDB.ObjectUtils.is(color) ? color[0] : color, "RGBA"); + let color0_1 = BDFDB.ColorUtils.setAlpha(color1_0, 0.1, "RGBA"); + let color0_7 = BDFDB.ColorUtils.setAlpha(color1_0, 0.7, "RGBA"); + renderedChildren.props.style = Object.assign({}, renderedChildren.props.style, { + background: color0_1, + color: color1_0 + }); + let onMouseEnter = renderedChildren.props.onMouseEnter || ( _ => {}); + renderedChildren.props.onMouseEnter = event => { + onMouseEnter(event); + event.target.style.setProperty("background", color0_7, "important"); + event.target.style.setProperty("color", "#FFFFFF", "important"); + }; + let onMouseLeave = renderedChildren.props.onMouseLeave || ( _ => {}); + renderedChildren.props.onMouseLeave = event => { + onMouseLeave(event); + event.target.style.setProperty("background", color0_1, "important"); + event.target.style.setProperty("color", color1_0, "important"); + }; + } + return renderedChildren; + } + } + break; + } + } + } + } + } + } + } + + changeAppTitle () { + let channel = BDFDB.LibraryModules.ChannelStore.getChannel(BDFDB.LibraryModules.LastChannelStore.getChannelId()); + let title = document.head.querySelector("title"); + if (title && BDFDB.ChannelUtils.isTextChannel(channel)) BDFDB.DOMUtils.setText(title, "@" + this.getChannelData(channel.id, BDFDB.DataUtils.get(this, "settings", "changeAppTitle")).name); + } + + changeChannelColor (child, channelId, modify) { + let color = this.getChannelDataColor(channelId); + if (color) { + color = modify ? this.chooseColor(color, modify) : BDFDB.ColorUtils.convert(color, "RGBA"); + let fontGradient = BDFDB.ObjectUtils.is(color); + if (fontGradient) child.props.children = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextGradientElement, { + gradient: BDFDB.ColorUtils.createGradient(color), + children: child.props.children + }); + else child.props.children = BDFDB.ReactUtils.createElement("span", { + style: {color: color}, + children: child.props.children + }); + } + } + + changeChannelIconColor (child, channelId, modify) { + let color = this.getChannelDataColor(channelId); + if (color && BDFDB.DataUtils.get(this, "settings", "changeChannelIcon")) { + color = modify ? this.chooseColor(BDFDB.ObjectUtils.is(color) ? color[0] : color, modify) : BDFDB.ColorUtils.convert(BDFDB.ObjectUtils.is(color) ? color[0] : color, "RGBA"); + child.props.color = color || "currentColor"; + if (color) { + child.props.foreground = null; + child.props.className = BDFDB.DOMUtils.formatClassName(child.props.className, "EC-changed"); + } + } + } + + chooseColor (color, config) { + if (color) { + if (BDFDB.ObjectUtils.is(config)) { + if (config.mentions || config.focused || config.hovered || config.selected || config.unread || config.connected) color = BDFDB.ColorUtils.change(color, 0.5); + else if (config.muted || config.locked) color = BDFDB.ColorUtils.change(color, -0.5); + } + return BDFDB.ColorUtils.convert(color, "RGBA"); + } + return null; + } + + getChannelDataColor (channelId) { + let channel = BDFDB.LibraryModules.ChannelStore.getChannel(channelId); + if (!channel) return null; + let channelData = BDFDB.DataUtils.load(this, "channels", channel.id); + if (channelData && channelData.color) return channelData.color; + let category = channel.parent_id && BDFDB.LibraryModules.ChannelStore.getChannel(channel.parent_id); + if (category) { + let categoryData = BDFDB.DataUtils.load(this, "channels", category.id); + if (categoryData && categoryData.inheritColor && categoryData.color) return categoryData.color; + } + return null; + } + + getChannelData (channelId, change = true) { + let channel = BDFDB.LibraryModules.ChannelStore.getChannel(channelId); + if (!channel) return new BDFDB.DiscordObjects.Channel({}); + let data = change && BDFDB.DataUtils.load(this, "channels", channel.id); + if (data) { + let nativeObject = new BDFDB.DiscordObjects.Channel(channel); + nativeObject.name = data.name || nativeObject.name; + return nativeObject; + } + return new BDFDB.DiscordObjects.Channel(channel); + } + + forceUpdateAll () { + this.changeAppTitle(); + BDFDB.ModuleUtils.forceAllUpdates(this); + BDFDB.ReactUtils.forceUpdate(BDFDB.ReactUtils.findOwner(document.querySelector(BDFDB.dotCN.app), {name:"Channel", unlimited:true})); + } + + openChannelSettingsModal (channel) { + let data = BDFDB.DataUtils.load(this, "channels", channel.id) || {}; + + BDFDB.ModalUtils.open(this, { + size: "MEDIUM", + header: this.labels.modal_header_text, + subheader: channel.name, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: this.labels.modal_channelname_text, + className: BDFDB.disCN.marginbottom20 + " input-channelname", + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + value: data.name, + placeholder: channel.name, + autoFocus: true + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, { + className: BDFDB.disCN.dividerdefault + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: this.labels.modal_colorpicker1_text, + className: BDFDB.disCN.marginbottom20, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ColorSwatches, { + color: data.color, + number: 1 + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Switch", + className: BDFDB.disCN.marginbottom20 + " input-inheritcolor", + label: this.labels.modal_inheritcolor_text, + tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H5, + value: channel.type == 4 && data.inheritColor, + disabled: channel.type != 4 + }) + ], + buttons: [{ + contents: BDFDB.LanguageUtils.LanguageStrings.SAVE, + color: "BRAND", + close: true, + click: modal => { + let olddata = Object.assign({}, data); + + data.name = modal.querySelector(".input-channelname " + BDFDB.dotCN.input).value.trim() || null; + + data.color = BDFDB.ColorUtils.getSwatchColor(modal, 1); + if (data.color != null && !BDFDB.ObjectUtils.is(data.color)) { + if (data.color[0] < 30 && data.color[1] < 30 && data.color[2] < 30) data.color = BDFDB.ColorUtils.change(data.color, 30); + else if (data.color[0] > 225 && data.color[1] > 225 && data.color[2] > 225) data.color = BDFDB.ColorUtils.change(data.color, -30); + } + + data.inheritColor = modal.querySelector(".input-inheritcolor " + BDFDB.dotCN.switchinner).checked; + + let changed = false; + if (Object.keys(data).every(key => data[key] == null || data[key] == false) && (changed = true)) BDFDB.DataUtils.remove(this, "channels", channel.id); + else if (!BDFDB.equals(olddata, data) && (changed = true)) BDFDB.DataUtils.save(data, this, "channels", channel.id); + if (changed) this.forceUpdateAll(); + } + }] + }); + } + + setLabelsByLanguage () { + switch (BDFDB.LanguageUtils.getLanguage().id) { + case "hr": //croatian + return { + context_localchannelsettings_text: "Postavke lokalnih kanala", + submenu_channelsettings_text: "Promijeni postavke", + submenu_resetsettings_text: "Vraćanje kanala", + modal_header_text: "Postavke lokalnih kanala", + modal_channelname_text: "Naziv lokalnog kanala", + modal_colorpicker1_text: "Boja lokalnog kanala", + modal_inheritcolor_text: "Naslijedi boju u potkanale" + }; + case "da": //danish + return { + context_localchannelsettings_text: "Lokal kanalindstillinger", + submenu_channelsettings_text: "Skift indstillinger", + submenu_resetsettings_text: "Nulstil kanal", + modal_header_text: "Lokal kanalindstillinger", + modal_channelname_text: "Lokalt kanalnavn", + modal_colorpicker1_text: "Lokal kanalfarve", + modal_inheritcolor_text: "Arve farve til subkanaler" + }; + case "de": //german + return { + context_localchannelsettings_text: "Lokale Kanaleinstellungen", + submenu_channelsettings_text: "Einstellungen ändern", + submenu_resetsettings_text: "Kanal zurücksetzen", + modal_header_text: "Lokale Kanaleinstellungen", + modal_channelname_text: "Lokaler Kanalname", + modal_colorpicker1_text: "Lokale Kanalfarbe", + modal_inheritcolor_text: "Farbe an Unterkanäle vererben" + }; + case "es": //spanish + return { + context_localchannelsettings_text: "Ajustes local de canal", + submenu_channelsettings_text: "Cambiar ajustes", + submenu_resetsettings_text: "Restablecer canal", + modal_header_text: "Ajustes local de canal", + modal_channelname_text: "Nombre local del canal", + modal_colorpicker1_text: "Color local del canal", + modal_inheritcolor_text: "Heredar color a sub-canales" + }; + case "fr": //french + return { + context_localchannelsettings_text: "Paramètres locale du salon", + submenu_channelsettings_text: "Modifier les paramètres", + submenu_resetsettings_text: "Réinitialiser le salon", + modal_header_text: "Paramètres locale du salon", + modal_channelname_text: "Nom local du salon", + modal_colorpicker1_text: "Couleur locale du salon", + modal_inheritcolor_text: "Hériter de la couleur sur les sous-salons" + }; + case "it": //italian + return { + context_localchannelsettings_text: "Impostazioni locale canale", + submenu_channelsettings_text: "Cambia impostazioni", + submenu_resetsettings_text: "Ripristina canale", + modal_header_text: "Impostazioni locale canale", + modal_channelname_text: "Nome locale canale", + modal_colorpicker1_text: "Colore locale canale", + modal_inheritcolor_text: "Eredita colore per sub-canali" + }; + case "nl": //dutch + return { + context_localchannelsettings_text: "Lokale kanaalinstellingen", + submenu_channelsettings_text: "Verandere instellingen", + submenu_resetsettings_text: "Reset kanaal", + modal_header_text: "Lokale kanaalinstellingen", + modal_channelname_text: "Lokale kanaalnaam", + modal_colorpicker1_text: "Lokale kanaalkleur", + modal_inheritcolor_text: "Overerving van kleuren naar subkanalen" + }; + case "no": //norwegian + return { + context_localchannelsettings_text: "Lokal kanalinnstillinger", + submenu_channelsettings_text: "Endre innstillinger", + submenu_resetsettings_text: "Tilbakestill kanal", + modal_header_text: "Lokal kanalinnstillinger", + modal_channelname_text: "Lokalt kanalnavn", + modal_colorpicker1_text: "Lokal kanalfarge", + modal_inheritcolor_text: "Arve farge til underkanaler" + }; + case "pl": //polish + return { + context_localchannelsettings_text: "Lokalne ustawienia kanału", + submenu_channelsettings_text: "Zmień ustawienia", + submenu_resetsettings_text: "Resetuj ustawienia", + modal_header_text: "Lokalne ustawienia kanału", + modal_channelname_text: "Lokalna nazwa kanału", + modal_colorpicker1_text: "Lokalny kolor kanału", + modal_inheritcolor_text: "Dziedzicz kolor do podkanałów" + }; + case "pt-BR": //portuguese (brazil) + return { + context_localchannelsettings_text: "Configurações local do canal", + submenu_channelsettings_text: "Mudar configurações", + submenu_resetsettings_text: "Redefinir canal", + modal_header_text: "Configurações local do canal", + modal_channelname_text: "Nome local do canal", + modal_colorpicker1_text: "Cor local do canal", + modal_inheritcolor_text: "Herdar cor aos sub-canais" + }; + case "fi": //finnish + return { + context_localchannelsettings_text: "Paikallinen kanavan asetukset", + submenu_channelsettings_text: "Vaihda asetuksia", + submenu_resetsettings_text: "Nollaa kanava", + modal_header_text: "Paikallinen kanavan asetukset", + modal_channelname_text: "Paikallinen kanavanimi", + modal_colorpicker1_text: "Paikallinen kanavanväri", + modal_inheritcolor_text: "Hävitä väri alikanaville" + }; + case "sv": //swedish + return { + context_localchannelsettings_text: "Lokal kanalinställningar", + submenu_channelsettings_text: "Ändra inställningar", + submenu_resetsettings_text: "Återställ kanal", + modal_header_text: "Lokal kanalinställningar", + modal_channelname_text: "Lokalt kanalnamn", + modal_colorpicker1_text: "Lokal kanalfärg", + modal_inheritcolor_text: "Inherit färg till subkanaler" + }; + case "tr": //turkish + return { + context_localchannelsettings_text: "Yerel Kanal Ayarları", + submenu_channelsettings_text: "Ayarları Değiştir", + submenu_resetsettings_text: "Kanal Sıfırla", + modal_header_text: "Yerel Kanal Ayarları", + modal_channelname_text: "Yerel Kanal Adı", + modal_colorpicker1_text: "Yerel Kanal Rengi", + modal_inheritcolor_text: "Renkleri alt kanallara miras alma" + }; + case "cs": //czech + return { + context_localchannelsettings_text: "Místní nastavení kanálu", + submenu_channelsettings_text: "Změnit nastavení", + submenu_resetsettings_text: "Obnovit kanál", + modal_header_text: "Místní nastavení kanálu", + modal_channelname_text: "Místní název kanálu", + modal_colorpicker1_text: "Místní barvy kanálu", + modal_inheritcolor_text: "Zdědit barvu na subkanály" + }; + case "bg": //bulgarian + return { + context_localchannelsettings_text: "Настройки за локални канали", + submenu_channelsettings_text: "Промяна на настройките", + submenu_resetsettings_text: "Възстановяване на канал", + modal_header_text: "Настройки за локални канали", + modal_channelname_text: "Локално име на канал", + modal_colorpicker1_text: "Локален цветен канал", + modal_inheritcolor_text: "Наследи цвета до подканали" + }; + case "ru": //russian + return { + context_localchannelsettings_text: "Настройки локального канала", + submenu_channelsettings_text: "Изменить настройки", + submenu_resetsettings_text: "Сбросить канал", + modal_header_text: "Настройки локального канала", + modal_channelname_text: "Имя локального канала", + modal_colorpicker1_text: "Цвет локального канала", + modal_inheritcolor_text: "Наследовать цвет на подканалы" + }; + case "uk": //ukrainian + return { + context_localchannelsettings_text: "Налаштування локального каналу", + submenu_channelsettings_text: "Змінити налаштування", + submenu_resetsettings_text: "Скидання каналу", + modal_header_text: "Налаштування локального каналу", + modal_channelname_text: "Локальне ім'я каналу", + modal_colorpicker1_text: "Колір місцевого каналу", + modal_inheritcolor_text: "Успадковують колір до підканалів" + }; + case "ja": //japanese + return { + context_localchannelsettings_text: "ローカルチャネル設定", + submenu_channelsettings_text: "設定を変更する", + submenu_resetsettings_text: "チャネルをリセットする", + modal_header_text: "ローカルチャネル設定", + modal_channelname_text: "ローカルチャネル名", + modal_colorpicker1_text: "ローカルチャネルの色", + modal_inheritcolor_text: "サブチャンネルに色を継承" + }; + case "zh-TW": //chinese (traditional) + return { + context_localchannelsettings_text: "本地頻道設置", + submenu_channelsettings_text: "更改設置", + submenu_resetsettings_text: "重置通道", + modal_header_text: "本地頻道設置", + modal_channelname_text: "本地頻道名稱", + modal_colorpicker1_text: "本地頻道顏色", + modal_inheritcolor_text: "繼承子通道的顏色" + }; + case "ko": //korean + return { + context_localchannelsettings_text: "로컬 채널 설정", + submenu_channelsettings_text: "설정 변경", + submenu_resetsettings_text: "채널 재설정", + modal_header_text: "로컬 채널 설정", + modal_channelname_text: "로컬 채널 이름", + modal_colorpicker1_text: "지역 채널 색깔", + modal_inheritcolor_text: "하위 채널에 색상 상속" + }; + default: //default: english + return { + context_localchannelsettings_text: "Local Channelsettings", + submenu_channelsettings_text: "Change Settings", + submenu_resetsettings_text: "Reset Channel", + modal_header_text: "Local Channelsettings", + modal_channelname_text: "Local Channelname", + modal_colorpicker1_text: "Local Channelcolor", + modal_inheritcolor_text: "Inherit color to Sub-Channels" + }; + } + } + } +})(); \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/EditServers.config.json b/.config/BetterDiscord/plugins/EditServers.config.json new file mode 100644 index 0000000..8544d34 --- /dev/null +++ b/.config/BetterDiscord/plugins/EditServers.config.json @@ -0,0 +1,28 @@ +{ + "changelog": { + "currentversion": "2.2.1" + }, + "servers": { + "624859996949315584": { + "banner": "https://media.discordapp.net/attachments/624865812888420352/715196061400039474/8PiLwbX.png", + "color1": null, + "color2": null, + "color3": null, + "color4": null, + "ignoreCustomName": false, + "name": "Gerrit", + "removeBanner": false, + "removeIcon": false, + "shortName": null, + "url": null + } + }, + "settings": { + "addOriginalTooltip": true, + "changeInGuildHeader": true, + "changeInGuildList": true, + "changeInMutualGuilds": true, + "changeInQuickSwitcher": true, + "changeInRecentMentions": true + } +} \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/EditServers.plugin.js b/.config/BetterDiscord/plugins/EditServers.plugin.js new file mode 100644 index 0000000..3291113 --- /dev/null +++ b/.config/BetterDiscord/plugins/EditServers.plugin.js @@ -0,0 +1,1079 @@ +//META{"name":"EditServers","authorId":"278543574059057154","invite":"Jx3TjNS","donate":"https://www.paypal.me/MircoWittrien","patreon":"https://www.patreon.com/MircoWittrien","website":"https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/EditServers","source":"https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/EditServers/EditServers.plugin.js"}*// + +var EditServers = (_ => { + return class EditServers { + getName () {return "EditServers";} + + getVersion () {return "2.2.1";} + + getAuthor () {return "DevilBro";} + + getDescription () {return "Allows you to change the icon, name and color of servers.";} + + constructor () { + this.changelog = { + "fixed":[["Context Menu Update","Fixes for the context menu update, yaaaaaay"]] + }; + + this.patchedModules = { + before: { + Guild: "render", + GuildIconWrapper: "render", + MutualGuilds: "render", + FriendRow: "render", + QuickSwitcher: "render", + QuickSwitchChannelResult: "render", + GuildSidebar: "render", + GuildHeader: "render" + }, + after: { + MessagesPopout: "render", + Guild: "render", + BlobMask: "render", + GuildIconWrapper: "render", + GuildIcon: "render", + GuildHeader: "render" + } + }; + + this.patchPriority = 7; + } + + initConstructor () { + this.defaults = { + settings: { + addOriginalTooltip: {value:true, inner:false, description:"Hovering over a changed Server Header shows the original Name as Tooltip"}, + changeInGuildList: {value:true, inner:true, description:"Server List"}, + changeInGuildHeader: {value:true, inner:true, description:"Server Header"}, + changeInMutualGuilds: {value:true, inner:true, description:"Mutual Servers"}, + changeInRecentMentions: {value:true, inner:true, description:"Recent Mentions Popout"}, + changeInQuickSwitcher: {value:true, inner:true, description:"Quick Switcher"} + } + }; + } + + getSettingsPanel () { + if (!window.BDFDB || typeof BDFDB != "object" || !BDFDB.loaded || !this.started) return; + let settings = BDFDB.DataUtils.get(this, "settings"); + let settingsPanel, settingsItems = [], innerItems = []; + + for (let key in settings) (!this.defaults.settings[key].inner ? settingsItems : innerItems).push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + className: BDFDB.disCN.marginbottom8, + type: "Switch", + plugin: this, + keys: ["settings", key], + label: this.defaults.settings[key].description, + value: settings[key] + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelInner, { + title: "Change Servers in:", + first: settingsItems.length == 0, + children: innerItems + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Button", + className: BDFDB.disCN.marginbottom8, + color: BDFDB.LibraryComponents.Button.Colors.RED, + label: "Reset all Servers", + onClick: _ => { + BDFDB.ModalUtils.confirm(this, "Are you sure you want to reset all Servers?", _ => { + BDFDB.DataUtils.remove(this, "servers"); + BDFDB.ModuleUtils.forceAllUpdates(this);; + }); + }, + children: BDFDB.LanguageUtils.LanguageStrings.RESET + })); + + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, settingsItems); + } + + // Legacy + load () {} + + start () { + if (!window.BDFDB) window.BDFDB = {myPlugins:{}}; + if (window.BDFDB && window.BDFDB.myPlugins && typeof window.BDFDB.myPlugins == "object") window.BDFDB.myPlugins[this.getName()] = this; + let libraryScript = document.querySelector("head script#BDFDBLibraryScript"); + if (!libraryScript || (performance.now() - libraryScript.getAttribute("date")) > 600000) { + if (libraryScript) libraryScript.remove(); + libraryScript = document.createElement("script"); + libraryScript.setAttribute("id", "BDFDBLibraryScript"); + libraryScript.setAttribute("type", "text/javascript"); + libraryScript.setAttribute("src", "https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.min.js"); + libraryScript.setAttribute("date", performance.now()); + libraryScript.addEventListener("load", _ => {this.initialize();}); + document.head.appendChild(libraryScript); + } + else if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) this.initialize(); + this.startTimeout = setTimeout(_ => { + try {return this.initialize();} + catch (err) {console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not initiate plugin! " + err);} + }, 30000); + } + + initialize () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + if (this.started) return; + BDFDB.PluginUtils.init(this); + + BDFDB.ModuleUtils.patch(this, BDFDB.LibraryModules.IconUtils, "getGuildBannerURL", {instead:e => { + let guild = BDFDB.LibraryModules.GuildStore.getGuild(e.methodArguments[0].id); + if (guild) { + if (e.methodArguments[0].id == "410787888507256842") return guild.banner; + let data = BDFDB.DataUtils.load(this, "servers", guild.id); + if (data && data.banner && !data.removeBanner) return data.banner; + } + return e.callOriginalMethod(); + }}); + + BDFDB.ModuleUtils.patch(this, BDFDB.LibraryComponents.GuildComponents.Guild.prototype, "render", { + before: e => {this.processGuild({instance:e.thisObject, returnvalue:e.returnValue, methodname:"render"});}, + after: e => {this.processGuild({instance:e.thisObject, returnvalue:e.returnValue, methodname:"render"});} + }); + + BDFDB.ModuleUtils.patch(this, BDFDB.LibraryComponents.Connectors.Link.prototype, "render", { + after: e => { + if (e.thisObject.props.className && e.thisObject.props.className.indexOf(BDFDB.disCN.guildiconwrapper) > -1) this.processGuildAcronym({instance:e.thisObject, returnvalue:e.returnValue, methodname:"render"}); + } + }); + + BDFDB.ModuleUtils.forceAllUpdates(this); + } + else console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not load BD functions!"); + } + + stop () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + this.stopping = true; + + BDFDB.ModuleUtils.forceAllUpdates(this); + + for (let guildobj of BDFDB.GuildUtils.getAll()) if (guildobj.instance) delete guildobj.instance.props.guild.EditServersCachedBanner; + + BDFDB.PluginUtils.clear(this); + } + } + + // Begin of own functions + + onGuildContextMenu (e) { + if (e.instance.props.guild) { + let [children, index] = BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: "devmode-copy-id", group: true}); + children.splice(index > -1 ? index : children.length, 0, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels.context_localserversettings_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "settings-submenu"), + children: BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: [ + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels.submenu_serversettings_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "settings-change"), + action: _ => { + BDFDB.ContextMenuUtils.close(e.instance); + this.openGuildSettingsModal(e.instance.props.guild.id); + } + }), + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels.submenu_resetsettings_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "settings-reset"), + disabled: !BDFDB.DataUtils.load(this, "servers", e.instance.props.guild.id), + action: _ => { + BDFDB.ContextMenuUtils.close(e.instance); + BDFDB.DataUtils.remove(this, "servers", e.instance.props.guild.id); + BDFDB.ModuleUtils.forceAllUpdates(this); + } + }) + ] + }) + }) + })); + } + } + + processGuild (e) { + if (BDFDB.GuildUtils.is(e.instance.props.guild) && BDFDB.DataUtils.get(this, "settings", "changeInGuildList")) { + e.instance.props.guild = this.getGuildData(e.instance.props.guild.id); + if (e.returnvalue) { + let data = BDFDB.DataUtils.load(this, "servers", e.instance.props.guild.id); + if (data && (data.color3 || data.color4)) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {name: ["GuildTooltip", "BDFDB_TooltipContainer"]}); + if (index > -1) children[index] = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + tooltipConfig: { + type: "right", + guild: e.instance.props.guild, + list: true, + offset: 12, + backgroundColor: data.color3, + fontColor: data.color4 + }, + children: children[index].props.children + }); + } + } + } + } + + processBlobMask (e) { + if (BDFDB.DataUtils.get(this, "settings", "changeInGuildList")) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {name: "NavItem"}); + if (index > -1 && children[index].props.to && children[index].props.to.pathname) { + let guild = BDFDB.LibraryModules.GuildStore.getGuild((children[index].props.to.pathname.split("/channels/")[1] || "").split("/")[0]); + if (guild) { + let data = BDFDB.DataUtils.load(this, "servers", guild.id); + if (data) { + if (data.shortName) children[index].props.name = data.shortName.split("").join(" "); + else if (data.name && data.ignoreCustomName) children[index].props.name = guild.name; + } + } + } + } + } + + processGuildAcronym (e) { + if (typeof e.returnvalue.props.children == "function" && BDFDB.DataUtils.get(this, "settings", "changeInGuildList")) { + let pathname = BDFDB.ReactUtils.getValue(e.instance, "props.to.pathname"); + let data = pathname && BDFDB.DataUtils.load(this, "servers", (pathname.split("/channels/")[1] || "").split("/")[0]); + if (data) { + let renderChildren = e.returnvalue.props.children; + e.returnvalue.props.children = (...args) => { + let renderedChildren = renderChildren(...args); + let [children, index] = BDFDB.ReactUtils.findChildren(renderedChildren, {props:[["className", BDFDB.disCN.guildiconacronym]]}); + if (index > -1) { + let fontGradient = BDFDB.ObjectUtils.is(data.color2); + children[index].props.style = Object.assign({}, children[index].props.style, { + background: BDFDB.ObjectUtils.is(data.color1) ? BDFDB.ColorUtils.createGradient(data.color1) : BDFDB.ColorUtils.convert(data.color1, "RGBA"), + color: !fontGradient && BDFDB.ColorUtils.convert(data.color2, "RGBA") + }); + if (fontGradient) children[index].props.children = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextGradientElement, { + gradient: BDFDB.ColorUtils.createGradient(data.color2), + children: children[index].props.children + }); + } + return renderedChildren; + }; + } + } + } + + processGuildIconWrapper (e) { + if (BDFDB.GuildUtils.is(e.instance.props.guild)) { + let settings = BDFDB.DataUtils.get(this, "settings"); + if (e.instance.props.className && e.instance.props.className.indexOf(BDFDB.disCN.guildfolderguildicon) > -1) e.instance.props.guild = this.getGuildData(e.instance.props.guild.id, settings.changeInGuildList); + else if (e.instance.props.className && e.instance.props.className.indexOf(BDFDB.disCN.listavatar) > -1) e.instance.props.guild = this.getGuildData(e.instance.props.guild.id, settings.changeInMutualGuilds); + else e.instance.props.guild = this.getGuildData(e.instance.props.guild.id); + } + } + + processGuildIcon (e) { + if (BDFDB.GuildUtils.is(e.instance.props.guild) && e.instance.props.style && (!e.instance.props.style.backgroundImage || e.instance.props.style.backgroundImage == "none")) { + let data = BDFDB.DataUtils.load(this, "servers", e.instance.props.guild.id); + if (data) { + let settings = BDFDB.DataUtils.get(this, "settings"); + if (e.instance.props.className && e.instance.props.className.indexOf(BDFDB.disCN.guildfolderguildicon) > -1) this.changeGuildIcon(e, data, settings.changeInGuildList); + else if (e.instance.props.className && e.instance.props.className.indexOf(BDFDB.disCN.listavatar) > -1 || BDFDB.ReactUtils.findConstructor(e.instance, "MutualGuild", {up: true})) this.changeGuildIcon(e, data, settings.changeInMutualGuilds); + else this.changeGuildIcon(e, data); + } + } + } + + processMutualGuilds (e) { + if (BDFDB.DataUtils.get(this, "settings", "changeInMutualGuilds")) for (let i in e.instance.props.mutualGuilds) e.instance.props.mutualGuilds[i].guild = this.getGuildData(e.instance.props.mutualGuilds[i].guild.id); + } + + processFriendRow (e) { + if (BDFDB.DataUtils.get(this, "settings", "changeInMutualGuilds")) for (let i in e.instance.props.mutualGuilds) e.instance.props.mutualGuilds[i] = this.getGuildData(e.instance.props.mutualGuilds[i].id); + } + + processQuickSwitcher (e) { + if (BDFDB.DataUtils.get(this, "settings", "changeInQuickSwitcher")) for (let i in e.instance.props.results) if (e.instance.props.results[i].type == "GUILD") e.instance.props.results[i].record = this.getGuildData(e.instance.props.results[i].record.id); + } + + processQuickSwitchChannelResult (e) { + if (e.instance.props.channel && e.instance.props.channel.guild_id && BDFDB.DataUtils.get(this, "settings", "changeInQuickSwitcher")) { + e.instance.props.children.props.children = this.getGuildData(e.instance.props.channel.guild_id).name; + } + } + + processMessagesPopout (e) { + if (BDFDB.DataUtils.get(this, "settings", "changeInRecentMentions")) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {name: "VerticalScroller"}); + if (index > -1 && children[index].props.children && BDFDB.ArrayUtils.is(children[index].props.children[0])) for (let i in children[index].props.children[0]) { + let divider = children[index].props.children[0][i]; + if (divider && divider.props && divider.props.className == BDFDB.disCN.messagespopoutchannelseparator) { + let channel = BDFDB.ReactUtils.findValue(children[index].props.children[0][parseInt(i)+1], "channel"); + if (BDFDB.ChannelUtils.isTextChannel(channel)) { + let [children2, index2] = BDFDB.ReactUtils.findChildren(divider, {props:[["className", BDFDB.disCN.messagespopoutguildname]]}); + if (index2 > -1) children2[index2].props.children = this.getGuildData(channel.guild_id).name; + } + } + } + } + } + + processGuildSidebar (e) { + if (e.instance.props.guild) { + let data = BDFDB.DataUtils.load(this, "servers", e.instance.props.guild.id); + if (data) { + if (data.removeBanner) e.instance.props.guild = new BDFDB.DiscordObjects.Guild(Object.assign({}, e.instance.props.guild, {banner: null})); + else if (data.banner) e.instance.props.guild = new BDFDB.DiscordObjects.Guild(Object.assign({}, e.instance.props.guild, {banner: data.banner})); + } + } + } + + processGuildHeader (e) { + if (e.instance.props.guild) { + let settings = BDFDB.DataUtils.get(this, "settings"); + if (settings.changeInGuildHeader) { + e.instance.props.guild = this.getGuildData(e.instance.props.guild.id); + let oldName = (BDFDB.LibraryModules.GuildStore.getGuild(e.instance.props.guild.id) || {}).name; + if (e.returnvalue && settings.addOriginalTooltip && oldName != e.instance.props.guild.name) { + e.returnvalue.props.children[0] = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: oldName, + children: e.returnvalue.props.children[0], + tooltipConfig: {type: "right"} + }); + } + } + } + } + + getGuildData (guildId, change = true) { + let guild = BDFDB.LibraryModules.GuildStore.getGuild(guildId); + if (!guild) return new BDFDB.DiscordObjects.Guild({}); + let data = change && BDFDB.DataUtils.load(this, "servers", guild.id); + if (data) { + let newGuildObject = {}, nativeObject = new BDFDB.DiscordObjects.Guild(guild); + for (let key in nativeObject) newGuildObject[key] = nativeObject[key]; + newGuildObject.name = data.name || nativeObject.name; + newGuildObject.acronym = data.shortName && data.shortName.replace(/\s/g, "") || BDFDB.LibraryModules.StringUtils.getAcronym(!data.ignoreCustomName && data.name || nativeObject.name); + if (data.removeIcon) { + newGuildObject.icon = null; + newGuildObject.getIconURL = _ => {return null;}; + } + else if (data.url) { + newGuildObject.icon = data.url; + newGuildObject.getIconURL = _ => {return data.url;}; + } + if (data.removeBanner) newGuildObject.banner = null; + else if (data.banner) newGuildObject.banner = data.banner; + return newGuildObject; + } + return new BDFDB.DiscordObjects.Guild(guild); + } + + changeGuildIcon (e, data, change = true) { + if (change) { + let fontGradient = BDFDB.ObjectUtils.is(data.color2); + e.returnvalue.props.style = Object.assign({}, e.returnvalue.props.style, { + background: BDFDB.ObjectUtils.is(data.color1) ? BDFDB.ColorUtils.createGradient(data.color1) : BDFDB.ColorUtils.convert(data.color1, "RGBA"), + color: !fontGradient && BDFDB.ColorUtils.convert(data.color2, "RGBA") + }); + if (fontGradient) e.returnvalue.props.children[0] = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextGradientElement, { + gradient: BDFDB.ColorUtils.createGradient(data.color2), + children: e.returnvalue.props.children[0] + }); + } + } + + openGuildSettingsModal (guildId) { + let guild = BDFDB.LibraryModules.GuildStore.getGuild(guildId); + if (!guild) return; + let data = BDFDB.DataUtils.load(this, "servers", guild.id) || {}; + + let currentIgnoreCustomNameState = data.ignoreCustomName; + + BDFDB.ModalUtils.open(this, { + size: "MEDIUM", + header: this.labels.modal_header_text, + subheader: guild.name, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, { + tab: this.labels.modal_tabheader1_text, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: this.labels.modal_guildname_text, + className: BDFDB.disCN.marginbottom20, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + className: "input-guildname", + key: "GUILDNAME", + value: data.name, + placeholder: guild.name, + autoFocus: true, + onChange: (value, instance) => { + if (!currentIgnoreCustomNameState) { + let acronymInputIns = BDFDB.ReactUtils.findOwner(instance._reactInternalFiber.return.return.return, {key: "GUILDACRONYM"}); + if (acronymInputIns) { + acronymInputIns.props.placeholder = value && BDFDB.LibraryModules.StringUtils.getAcronym(value) || guild.acronym; + BDFDB.ReactUtils.forceUpdate(acronymInputIns); + } + } + } + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: this.labels.modal_guildacronym_text, + className: BDFDB.disCN.marginbottom8, + children: + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + className: "input-guildacronym", + key: "GUILDACRONYM", + value: data.shortName, + placeholder: !data.ignoreCustomName && data.name && BDFDB.LibraryModules.StringUtils.getAcronym(data.name) || guild.acronym + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Switch", + className: BDFDB.disCN.marginbottom20 + " input-ignorecustomname", + label: this.labels.modal_ignorecustomname_text, + tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H5, + value: data.ignoreCustomName, + onChange: (value, instance) => { + currentIgnoreCustomNameState = value; + let nameInputIns = BDFDB.ReactUtils.findOwner(instance._reactInternalFiber.return, {key: "GUILDNAME"}); + let acronymInputIns = BDFDB.ReactUtils.findOwner(instance._reactInternalFiber.return, {key: "GUILDACRONYM"}); + if (nameInputIns && acronymInputIns) { + acronymInputIns.props.placeholder = !value && nameInputIns.props.value && BDFDB.LibraryModules.StringUtils.getAcronym(nameInputIns.props.value) || guild.acronym; + BDFDB.ReactUtils.forceUpdate(acronymInputIns); + } + } + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.marginbottom20, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.marginbottom8, + align: BDFDB.LibraryComponents.Flex.Align.CENTER, + direction: BDFDB.LibraryComponents.Flex.Direction.HORIZONTAL, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormTitle, { + className: BDFDB.disCN.marginreset, + tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H5, + children: this.labels.modal_guildicon_text + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + className: "input-removeicon", + type: "Switch", + grow: 0, + label: BDFDB.LanguageUtils.LanguageStrings.REMOVE, + tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H5, + value: data.removeIcon, + onChange: (value, instance) => { + let iconInputIins = BDFDB.ReactUtils.findOwner(instance._reactInternalFiber.return.return, {key: "GUILDICON"}); + if (iconInputIins) { + delete iconInputIins.props.success; + delete iconInputIins.props.errorMessage; + iconInputIins.props.disabled = value; + BDFDB.ReactUtils.forceUpdate(iconInputIins); + } + } + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + className: "input-guildicon", + key: "GUILDICON", + success: !data.removeIcon && data.url, + maxLength: 100000000000000000000, + value: data.url, + placeholder: BDFDB.GuildUtils.getIcon(guild.id), + disabled: data.removeIcon, + onChange: (value, instance) => { + this.checkUrl(value, instance); + } + }) + ] + }), + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.marginbottom20, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.marginbottom8, + align: BDFDB.LibraryComponents.Flex.Align.CENTER, + direction: BDFDB.LibraryComponents.Flex.Direction.HORIZONTAL, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormTitle, { + className: BDFDB.disCN.marginreset, + tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H5, + children: this.labels.modal_guildbanner_text + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + className: "input-removebanner", + type: "Switch", + grow: 0, + label: BDFDB.LanguageUtils.LanguageStrings.REMOVE, + tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H5, + value: data.removeBanner && guild.id != "410787888507256842", + disabled: guild.id == "410787888507256842", + onChange: (value, instance) => { + let bannerInputIns = BDFDB.ReactUtils.findOwner(instance._reactInternalFiber.return.return, {key: "GUILDBANNER"}); + if (bannerInputIns) { + delete bannerInputIns.props.success; + delete bannerInputIns.props.errorMessage; + bannerInputIns.props.disabled = value; + BDFDB.ReactUtils.forceUpdate(bannerInputIns); + } + } + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + className: "input-guildbanner", + key: "GUILDBANNER", + success: !data.removeBanner && data.banner, + maxLength: 100000000000000000000, + value: data.banner, + placeholder: BDFDB.GuildUtils.getBanner(guild.id), + disabled: data.removeBanner || guild.id == "410787888507256842", + onChange: (value, instance) => { + this.checkUrl(value, instance); + } + }) + ] + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, { + tab: this.labels.modal_tabheader2_text, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: this.labels.modal_colorpicker1_text, + className: BDFDB.disCN.marginbottom20, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ColorSwatches, { + color: data.color1, + number: 1 + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: this.labels.modal_colorpicker2_text, + className: BDFDB.disCN.marginbottom20, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ColorSwatches, { + color: data.color2, + number: 2 + }) + ] + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, { + tab: this.labels.modal_tabheader3_text, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: this.labels.modal_colorpicker3_text, + className: BDFDB.disCN.marginbottom20, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ColorSwatches, { + color: data.color3, + number: 3 + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: this.labels.modal_colorpicker4_text, + className: BDFDB.disCN.marginbottom20, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ColorSwatches, { + color: data.color4, + number: 4 + }) + ] + }) + ] + }) + ], + buttons: [{ + contents: BDFDB.LanguageUtils.LanguageStrings.SAVE, + color: "BRAND", + close: true, + click: modal => { + let olddata = Object.assign({}, data); + + let guildnameinput = modal.querySelector(".input-guildname " + BDFDB.dotCN.input); + let guildacronyminput = modal.querySelector(".input-guildacronym " + BDFDB.dotCN.input); + let ignorecustomnameinput = modal.querySelector(".input-ignorecustomname " + BDFDB.dotCN.switchinner); + let guildiconinput = modal.querySelector(".input-guildicon " + BDFDB.dotCN.input); + let removeiconinput = modal.querySelector(".input-removeicon " + BDFDB.dotCN.switchinner); + let guildbannerinput = modal.querySelector(".input-guildbanner " + BDFDB.dotCN.input); + let removebannerinput = modal.querySelector(".input-removebanner " + BDFDB.dotCN.switchinner); + + data.name = guildnameinput.value.trim() || null; + data.shortName = guildacronyminput.value.trim() || null; + data.ignoreCustomName = ignorecustomnameinput.checked; + data.url = (!data.removeIcon && BDFDB.DOMUtils.containsClass(guildiconinput, BDFDB.disCN.inputsuccess) ? guildiconinput.value.trim() : null) || null; + data.removeIcon = removeiconinput.checked; + data.banner = (!data.removeBanner && BDFDB.DOMUtils.containsClass(guildbannerinput, BDFDB.disCN.inputsuccess) ? guildbannerinput.value.trim() : null) || null; + data.removeBanner = removebannerinput.checked && guild.id != "410787888507256842"; + + data.color1 = BDFDB.ColorUtils.getSwatchColor(modal, 1); + data.color2 = BDFDB.ColorUtils.getSwatchColor(modal, 2); + data.color3 = BDFDB.ColorUtils.getSwatchColor(modal, 3); + data.color4 = BDFDB.ColorUtils.getSwatchColor(modal, 4); + + let changed = false; + if (Object.keys(data).every(key => !data[key]) && (changed = true)) BDFDB.DataUtils.remove(this, "servers", guild.id); + else if (!BDFDB.equals(olddata, data) && (changed = true)) BDFDB.DataUtils.save(data, this, "servers", guild.id); + if (changed) BDFDB.ModuleUtils.forceAllUpdates(this);; + } + }] + }); + } + + checkUrl (url, instance) { + BDFDB.TimeUtils.clear(instance.checkTimeout); + if (url == null || !url.trim()) { + delete instance.props.success; + delete instance.props.errorMessage; + instance.forceUpdate(); + } + else instance.checkTimeout = BDFDB.TimeUtils.timeout(_ => { + BDFDB.LibraryRequires.request(url.trim(), (error, response, result) => { + if (response && response.headers["content-type"] && response.headers["content-type"].indexOf("image") != -1) { + instance.props.success = true; + delete instance.props.errorMessage; + } + else { + delete instance.props.success; + instance.props.errorMessage = this.labels.modal_invalidurl_text; + } + delete instance.checkTimeout; + instance.forceUpdate(); + }); + }, 1000); + } + + setBanner (id, data) { + data = data || {}; + let guild = BDFDB.LibraryModules.GuildStore.getGuild(id); + if (!guild) return; + if (guild.EditServersCachedBanner === undefined) guild.EditServersCachedBanner = guild.banner; + guild.banner = data.removeBanner ? null : (data.banner || guild.EditServersCachedBanner); + } + + setLabelsByLanguage () { + switch (BDFDB.LanguageUtils.getLanguage().id) { + case "hr": //croatian + return { + context_localserversettings_text: "Lokalne postavke poslužitelja", + submenu_serversettings_text: "Promijeni postavke", + submenu_resetsettings_text: "Ponovno postavite poslužitelj", + modal_header_text: "Lokalne postavke poslužitelja", + modal_guildname_text: "Naziv lokalnog poslužitelja", + modal_guildacronym_text: "Akronim lokalnog poslužitelja", + modal_ignorecustomname_text: "Koristite izvorno ime poslužitelja za akronim poslužitelja", + modal_guildicon_text: "Ikona", + modal_guildbanner_text: "Baner", + modal_tabheader1_text: "Poslužitelja", + modal_tabheader2_text: "Boja ikona", + modal_tabheader3_text: "Boja tooltip", + modal_colorpicker1_text: "Boja ikona", + modal_colorpicker2_text: "Boja fonta", + modal_colorpicker3_text: "Boja tooltip", + modal_colorpicker4_text: "Boja fonta", + modal_invalidurl_text: "Nevažeći URL" + }; + case "da": //danish + return { + context_localserversettings_text: "Lokal serverindstillinger", + submenu_serversettings_text: "Skift indstillinger", + submenu_resetsettings_text: "Nulstil server", + modal_header_text: "Lokal serverindstillinger", + modal_guildname_text: "Lokalt servernavn", + modal_guildacronym_text: "Lokalt serverakronym", + modal_ignorecustomname_text: "Brug det originale servernavn til serverens akronym", + modal_guildicon_text: "Ikon", + modal_guildbanner_text: "Banner", + modal_tabheader1_text: "Server", + modal_tabheader2_text: "Ikonfarve", + modal_tabheader3_text: "Tooltipfarve", + modal_colorpicker1_text: "Ikonfarve", + modal_colorpicker2_text: "Skriftfarve", + modal_colorpicker3_text: "Tooltipfarve", + modal_colorpicker4_text: "Skriftfarve", + modal_invalidurl_text: "Ugyldig URL" + }; + case "de": //german + return { + context_localserversettings_text: "Lokale Servereinstellungen", + submenu_serversettings_text: "Einstellungen ändern", + submenu_resetsettings_text: "Server zurücksetzen", + modal_header_text: "Lokale Servereinstellungen", + modal_guildname_text: "Lokaler Servername", + modal_guildacronym_text: "Lokales Serverkürzel", + modal_ignorecustomname_text: "Benutze den ursprünglichen Servernamen für das Serverkürzel", + modal_guildicon_text: "Icon", + modal_guildbanner_text: "Banner", + modal_tabheader1_text: "Server", + modal_tabheader2_text: "Iconfarbe", + modal_tabheader3_text: "Tooltipfarbe", + modal_colorpicker1_text: "Iconfarbe", + modal_colorpicker2_text: "Schriftfarbe", + modal_colorpicker3_text: "Tooltipfarbe", + modal_colorpicker4_text: "Schriftfarbe", + modal_invalidurl_text: "Ungültige URL" + }; + case "es": //spanish + return { + context_localserversettings_text: "Ajustes local de servidor", + submenu_serversettings_text: "Cambiar ajustes", + submenu_resetsettings_text: "Restablecer servidor", + modal_header_text: "Ajustes local de servidor", + modal_guildname_text: "Nombre local del servidor", + modal_guildacronym_text: "Acrónimo local del servidor", + modal_ignorecustomname_text: "Use el nombre del servidor original para el acrónimo del servidor", + modal_guildicon_text: "Icono", + modal_guildbanner_text: "Bandera", + modal_tabheader1_text: "Servidor", + modal_tabheader2_text: "Color del icono", + modal_tabheader3_text: "Color de tooltip", + modal_colorpicker1_text: "Color del icono", + modal_colorpicker2_text: "Color de fuente", + modal_colorpicker3_text: "Color de tooltip", + modal_colorpicker4_text: "Color de fuente", + modal_invalidurl_text: "URL inválida" + }; + case "fr": //french + return { + context_localserversettings_text: "Paramètres locale du serveur", + submenu_serversettings_text: "Modifier les paramètres", + submenu_resetsettings_text: "Réinitialiser le serveur", + modal_header_text: "Paramètres locale du serveur", + modal_guildname_text: "Nom local du serveur", + modal_guildacronym_text: "Acronyme local de serveur", + modal_ignorecustomname_text: "Utilisez le nom de serveur d'origine pour l'acronyme de serveur", + modal_guildicon_text: "Icône", + modal_guildbanner_text: "Bannière", + modal_tabheader1_text: "Serveur", + modal_tabheader2_text: "Couleur de l'icône", + modal_tabheader3_text: "Couleur de tooltip", + modal_colorpicker1_text: "Couleur de l'icône", + modal_colorpicker2_text: "Couleur de la police", + modal_colorpicker3_text: "Couleur de tooltip", + modal_colorpicker4_text: "Couleur de la police", + modal_invalidurl_text: "URL invalide" + }; + case "it": //italian + return { + context_localserversettings_text: "Impostazioni locale server", + submenu_serversettings_text: "Cambia impostazioni", + submenu_resetsettings_text: "Ripristina server", + modal_header_text: "Impostazioni locale server", + modal_guildname_text: "Nome locale server", + modal_guildacronym_text: "Acronimo locale server", + modal_ignorecustomname_text: "Utilizzare il nome del server originale per l'acronimo del server", + modal_guildicon_text: "Icona", + modal_guildbanner_text: "Bandiera", + modal_tabheader1_text: "Server", + modal_tabheader2_text: "Colore dell'icona", + modal_tabheader3_text: "Colore della tooltip", + modal_colorpicker1_text: "Colore dell'icona", + modal_colorpicker2_text: "Colore del carattere", + modal_colorpicker3_text: "Colore della tooltip", + modal_colorpicker4_text: "Colore del carattere", + modal_invalidurl_text: "URL non valido" + }; + case "nl": //dutch + return { + context_localserversettings_text: "Lokale serverinstellingen", + submenu_serversettings_text: "Verandere instellingen", + submenu_resetsettings_text: "Reset server", + modal_header_text: "Lokale serverinstellingen", + modal_guildname_text: "Lokale servernaam", + modal_guildacronym_text: "Lokale server acroniem", + modal_ignorecustomname_text: "Gebruik de oorspronkelijke servernaam voor het serveracrononiem", + modal_guildicon_text: "Icoon", + modal_guildbanner_text: "Banier", + modal_tabheader1_text: "Server", + modal_tabheader2_text: "Icoonkleur", + modal_tabheader3_text: "Tooltipkleur", + modal_colorpicker1_text: "Icoonkleur", + modal_colorpicker2_text: "Doopvontkleur", + modal_colorpicker3_text: "Tooltipkleur", + modal_colorpicker4_text: "Doopvontkleur", + modal_invalidurl_text: "Ongeldige URL" + }; + case "no": //norwegian + return { + context_localserversettings_text: "Lokal serverinnstillinger", + submenu_serversettings_text: "Endre innstillinger", + submenu_resetsettings_text: "Tilbakestill server", + modal_header_text: "Lokal serverinnstillinger", + modal_guildname_text: "Lokalt servernavn", + modal_guildacronym_text: "Lokalt serverforkortelse", + modal_ignorecustomname_text: "Bruk det originale servernavnet til serverforkortelsen", + modal_guildicon_text: "Ikon", + modal_guildbanner_text: "Banner", + modal_tabheader1_text: "Server", + modal_tabheader2_text: "Ikonfarge", + modal_tabheader3_text: "Tooltipfarge", + modal_colorpicker1_text: "Ikonfarge", + modal_colorpicker2_text: "Skriftfarge", + modal_colorpicker3_text: "Tooltipfarge", + modal_colorpicker4_text: "Skriftfarge", + modal_invalidurl_text: "Ugyldig URL" + }; + case "pl": //polish + return { + context_localserversettings_text: "Lokalne ustawienia serwera", + submenu_serversettings_text: "Zmień ustawienia", + submenu_resetsettings_text: "Resetuj ustawienia", + modal_header_text: "Lokalne ustawienia serwera", + modal_guildname_text: "Lokalna nazwa serwera", + modal_guildacronym_text: "Akronim lokalnego serwera", + modal_ignorecustomname_text: "Użyj oryginalnej nazwy serwera dla akronimu serwera", + modal_guildicon_text: "Ikona", + modal_guildbanner_text: "Baner", + modal_tabheader1_text: "Serwer", + modal_tabheader2_text: "Kolor ikony", + modal_tabheader3_text: "Kolor podpowiedzi", + modal_colorpicker1_text: "Kolor ikony", + modal_colorpicker2_text: "Kolor czcionki", + modal_colorpicker3_text: "Kolor podpowiedzi", + modal_colorpicker4_text: "Kolor czcionki", + modal_invalidurl_text: "Nieprawidłowe URL" + }; + case "pt-BR": //portuguese (brazil) + return { + context_localserversettings_text: "Configurações local do servidor", + submenu_serversettings_text: "Mudar configurações", + submenu_resetsettings_text: "Redefinir servidor", + modal_header_text: "Configurações local do servidor", + modal_guildname_text: "Nome local do servidor", + modal_guildacronym_text: "Acrônimo local de servidor", + modal_ignorecustomname_text: "Use o nome do servidor original para a sigla do servidor", + modal_guildicon_text: "Icone", + modal_guildbanner_text: "Bandeira", + modal_tabheader1_text: "Servidor", + modal_tabheader2_text: "Cor do ícone", + modal_tabheader3_text: "Cor da tooltip", + modal_colorpicker1_text: "Cor do ícone", + modal_colorpicker2_text: "Cor da fonte", + modal_colorpicker3_text: "Cor da tooltip", + modal_colorpicker4_text: "Cor da fonte", + modal_invalidurl_text: "URL inválida" + }; + case "fi": //finnish + return { + context_localserversettings_text: "Paikallinen palvelimen asetukset", + submenu_serversettings_text: "Vaihda asetuksia", + submenu_resetsettings_text: "Nollaa palvelimen", + modal_header_text: "Paikallinen palvelimen asetukset", + modal_guildname_text: "Paikallinen palvelimenimi", + modal_guildacronym_text: "Paikallisen palvelimen lyhenne", + modal_ignorecustomname_text: "Käytä alkuperäistä palvelimen nimeä palvelimen lyhenteessä", + modal_guildicon_text: "Ikonin", + modal_guildbanner_text: "Banneri", + modal_tabheader1_text: "Palvelimen", + modal_tabheader2_text: "Ikoninväri", + modal_tabheader3_text: "Tooltipväri", + modal_colorpicker1_text: "Ikoninväri", + modal_colorpicker2_text: "Fontinväri", + modal_colorpicker3_text: "Tooltipväri", + modal_colorpicker4_text: "Fontinväri", + modal_invalidurl_text: "Virheellinen URL" + }; + case "sv": //swedish + return { + context_localserversettings_text: "Lokal serverinställningar", + submenu_serversettings_text: "Ändra inställningar", + submenu_resetsettings_text: "Återställ server", + modal_header_text: "Lokal serverinställningar", + modal_guildname_text: "Lokalt servernamn", + modal_guildacronym_text: "Lokal server förkortning", + modal_ignorecustomname_text: "Använd det ursprungliga servernamnet för serverförkortningen", + modal_guildicon_text: "Ikon", + modal_guildbanner_text: "Banderoll", + modal_tabheader1_text: "Server", + modal_tabheader2_text: "Ikonfärg", + modal_tabheader3_text: "Tooltipfärg", + modal_colorpicker1_text: "Ikonfärg", + modal_colorpicker2_text: "Fontfärg", + modal_colorpicker3_text: "Tooltipfärg", + modal_colorpicker4_text: "Fontfärg", + modal_invalidurl_text: "Ogiltig URL" + }; + case "tr": //turkish + return { + context_localserversettings_text: "Yerel Sunucu Ayarları", + submenu_serversettings_text: "Ayarları Değiştir", + submenu_resetsettings_text: "Sunucu Sıfırla", + modal_header_text: "Yerel sunucu ayarları", + modal_guildname_text: "Yerel sunucu adı", + modal_guildacronym_text: "Yerel sunucu kısaltması", + modal_ignorecustomname_text: "Sunucu kısaltması için orijinal sunucu adını kullanın", + modal_guildicon_text: "Simge", + modal_guildbanner_text: "Afişi", + modal_tabheader1_text: "Sunucu", + modal_tabheader2_text: "Simge rengi", + modal_tabheader3_text: "Tooltip rengi", + modal_colorpicker1_text: "Simge rengi", + modal_colorpicker2_text: "Yazı rengi", + modal_colorpicker3_text: "Tooltip rengi", + modal_colorpicker4_text: "Yazı rengi", + modal_invalidurl_text: "Geçersiz URL" + }; + case "cs": //czech + return { + context_localserversettings_text: "Místní nastavení serveru", + submenu_serversettings_text: "Změnit nastavení", + submenu_resetsettings_text: "Obnovit server", + modal_header_text: "Místní nastavení serveru", + modal_guildname_text: "Místní název serveru", + modal_guildacronym_text: "Zkratka místního serveru", + modal_ignorecustomname_text: "Pro zkratku serveru použijte původní název serveru", + modal_guildicon_text: "Ikony", + modal_guildbanner_text: "Prapor", + modal_tabheader1_text: "Server", + modal_tabheader2_text: "Barva ikony", + modal_tabheader3_text: "Barva tooltip", + modal_colorpicker1_text: "Barva ikony", + modal_colorpicker2_text: "Barva fontu", + modal_colorpicker3_text: "Barva tooltip", + modal_colorpicker4_text: "Barva fontu", + modal_invalidurl_text: "Neplatná URL" + }; + case "bg": //bulgarian + return { + context_localserversettings_text: "Настройки за локални cървър", + submenu_serversettings_text: "Промяна на настройките", + submenu_resetsettings_text: "Възстановяване на cървър", + modal_header_text: "Настройки за локални cървър", + modal_guildname_text: "Локално име на cървър", + modal_guildacronym_text: "Акроним на локалния сървър", + modal_ignorecustomname_text: "Използвайте оригиналното име на сървъра за съкращението на сървъра", + modal_guildicon_text: "Икона", + modal_guildbanner_text: "Знаме", + modal_tabheader1_text: "Cървър", + modal_tabheader2_text: "Цвят на иконата", + modal_tabheader3_text: "Цвят на подсказка", + modal_colorpicker1_text: "Цвят на иконата", + modal_colorpicker2_text: "Цвят на шрифта", + modal_colorpicker3_text: "Цвят на подсказка", + modal_colorpicker4_text: "Цвят на шрифта", + modal_invalidurl_text: "Невалиден URL" + }; + case "ru": //russian + return { + context_localserversettings_text: "Настройки локального cервер", + submenu_serversettings_text: "Изменить настройки", + submenu_resetsettings_text: "Сбросить cервер", + modal_header_text: "Настройки локального cервер", + modal_guildname_text: "Имя локального cервер", + modal_guildacronym_text: "Акроним локального сервера", + modal_ignorecustomname_text: "Используйте оригинальное имя сервера для сокращения сервера", + modal_guildicon_text: "Значок", + modal_guildbanner_text: "Баннер", + modal_tabheader1_text: "Cервер", + modal_tabheader2_text: "Цвет значков", + modal_tabheader3_text: "Цвет подсказка", + modal_colorpicker1_text: "Цвет значков", + modal_colorpicker2_text: "Цвет шрифта", + modal_colorpicker3_text: "Цвет подсказка", + modal_colorpicker4_text: "Цвет шрифта", + modal_invalidurl_text: "Неверная URL" + }; + case "uk": //ukrainian + return { + context_localserversettings_text: "Налаштування локального cервер", + submenu_serversettings_text: "Змінити налаштування", + submenu_resetsettings_text: "Скидання cервер", + modal_header_text: "Налаштування локального cервер", + modal_guildname_text: "Локальне ім'я cервер", + modal_guildacronym_text: "Акронім локального сервера", + modal_ignorecustomname_text: "Використовуйте оригінальне ім'я сервера для абревіатури сервера", + modal_guildicon_text: "Іконка", + modal_guildbanner_text: "Банер", + modal_tabheader1_text: "Cервер", + modal_tabheader2_text: "Колір ікони", + modal_tabheader3_text: "Колір підказка", + modal_colorpicker1_text: "Колір ікони", + modal_colorpicker2_text: "Колір шрифту", + modal_colorpicker3_text: "Колір підказка", + modal_colorpicker4_text: "Колір шрифту", + modal_invalidurl_text: "Недійсна URL" + }; + case "ja": //japanese + return { + context_localserversettings_text: "ローカルサーバー設定", + submenu_serversettings_text: "設定を変更する", + submenu_resetsettings_text: "サーバーをリセットする", + modal_header_text: "ローカルサーバー設定", + modal_guildname_text: "ローカルサーバー名", + modal_guildacronym_text: "ローカルサーバーの頭字語", + modal_ignorecustomname_text: "サーバーの頭字語に元のサーバー名を使用する", + modal_guildicon_text: "アイコン", + modal_guildbanner_text: "バナー", + modal_tabheader1_text: "サーバー", + modal_tabheader2_text: "アイコンの色", + modal_tabheader3_text: "ツールチップの色", + modal_colorpicker1_text: "アイコンの色", + modal_colorpicker2_text: "フォントの色", + modal_colorpicker3_text: "ツールチップの色", + modal_colorpicker4_text: "フォントの色", + modal_invalidurl_text: "無効な URL" + }; + case "zh-TW": //chinese (traditional) + return { + context_localserversettings_text: "本地服務器設置", + submenu_serversettings_text: "更改設置", + submenu_resetsettings_text: "重置服務器", + modal_header_text: "本地服務器設置", + modal_guildname_text: "服務器名稱", + modal_guildacronym_text: "本地服務器縮寫", + modal_ignorecustomname_text: "使用原始服務器名稱作為服務器首字母縮寫", + modal_guildicon_text: "圖標", + modal_guildbanner_text: "旗幟", + modal_tabheader1_text: "服務器", + modal_tabheader2_text: "圖標顏色", + modal_tabheader3_text: "工具提示顏色", + modal_colorpicker1_text: "圖標顏色", + modal_colorpicker2_text: "字體顏色", + modal_colorpicker3_text: "工具提示顏色", + modal_colorpicker4_text: "字體顏色", + modal_invalidurl_text: "無效的 URL" + }; + case "ko": //korean + return { + context_localserversettings_text: "로컬 서버 설정", + submenu_serversettings_text: "설정 변경", + submenu_resetsettings_text: "서버 재설정", + modal_header_text: "로컬 서버 설정", + modal_guildname_text: "로컬 서버 이름", + modal_guildacronym_text: "로컬 서버 약어", + modal_ignorecustomname_text: "서버 약어에 원래 서버 이름을 사용하십시오", + modal_guildicon_text: "상", + modal_guildbanner_text: "기치", + modal_tabheader1_text: "서버", + modal_tabheader2_text: "상 색깔", + modal_tabheader3_text: "툴팁 색깔", + modal_colorpicker1_text: "상 색깔", + modal_colorpicker2_text: "글꼴 색깔", + modal_colorpicker3_text: "툴팁 색깔", + modal_colorpicker4_text: "글꼴 색깔", + modal_invalidurl_text: "잘못된 URL" + }; + default: //default: english + return { + context_localserversettings_text: "Local Serversettings", + submenu_serversettings_text: "Change Settings", + submenu_resetsettings_text: "Reset Server", + modal_header_text: "Local Serversettings", + modal_guildname_text: "Local Servername", + modal_guildacronym_text: "Local Serveracronym", + modal_ignorecustomname_text: "Use the original Servername for the Serveracronym", + modal_guildicon_text: "Icon", + modal_guildbanner_text: "Banner", + modal_tabheader1_text: "Server", + modal_tabheader2_text: "Iconcolor", + modal_tabheader3_text: "Tooltipcolor", + modal_colorpicker1_text: "Iconcolor", + modal_colorpicker2_text: "Fontcolor", + modal_colorpicker3_text: "Tooltipcolor", + modal_colorpicker4_text: "Fontcolor", + modal_invalidurl_text: "Invalid URL" + }; + } + } + } +})(); \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/NotificationSounds.config.json b/.config/BetterDiscord/plugins/NotificationSounds.config.json new file mode 100644 index 0000000..5101ee4 --- /dev/null +++ b/.config/BetterDiscord/plugins/NotificationSounds.config.json @@ -0,0 +1,356 @@ +{ + "audios": { + "---": { + "---": null + }, + "Default": { + "Communication Channel": "https://notificationsounds.com/soundfiles/63538fe6ef330c13a05a3ed7e599d5f7/file-sounds-917-communication-channel.wav", + "Isn't it": "https://notificationsounds.com/soundfiles/ba2fd310dcaa8781a9a652a31baf3c68/file-sounds-969-isnt-it.wav", + "Job Done": "https://notificationsounds.com/soundfiles/5b69b9cb83065d403869739ae7f0995e/file-sounds-937-job-done.wav", + "Served": "https://notificationsounds.com/soundfiles/b337e84de8752b27eda3a12363109e80/file-sounds-913-served.wav", + "Solemn": "https://notificationsounds.com/soundfiles/53fde96fcc4b4ce72d7739202324cd49/file-sounds-882-solemn.wav", + "System Fault": "https://notificationsounds.com/soundfiles/ebd9629fc3ae5e9f6611e2ee05a31cef/file-sounds-990-system-fault.wav", + "You wouldn't believe": "https://notificationsounds.com/soundfiles/087408522c31eeb1f982bc0eaf81d35f/file-sounds-949-you-wouldnt-believe.wav" + }, + "Discord": { + "HotKeys Window Down": "/assets/71f048f8aa7d4b24bf4268a87cbbb192.mp3", + "HotKeys Window Left": "/assets/1de04408e62b5d52ae3ebbb91e9e1978.mp3", + "HotKeys Window Right": "/assets/2c0433f93db8449e4a82b76dc520cb29.mp3", + "HotKeys Window Up": "/assets/68472713f7a62c7c37e0a6a5d5a1faeb.mp3", + "Human Man Voice": "/assets/a37dcd6272ae41cf49295d58c9806fe3.mp3", + "Incoming Call": "/assets/84a1b4e11d634dbfa1e5dd97a96de3ad.mp3", + "Incoming Call Beat": "/assets/b9411af07f154a6fef543e7e442e4da9.mp3", + "Mention Ping": "/assets/fa4d62c3cbc80733bf1f01b9c6f181de.mp3", + "Mention Ping 2": "/assets/a5f42064e8120e381528b14fd3188b72.mp3", + "Mention Ping 3": "/assets/84c9fa3d07da865278bd77c97d952db4.mp3", + "New Chatmessage": "/assets/dd920c06a01e5bb8b09678581e29d56f.mp3", + "New Chatmessage 2": "/assets/15fe810f6cfab609c7fcda61652b9b34.mp3", + "New Chatmessage 3": "/assets/53ce6a92d3c233e8b4ac529d34d374e4.mp3", + "Outgoing Call": "/assets/c6e92752668dde4eee5923d70441579f.mp3", + "Overlay Unlocked": "/assets/ad322ffe0a88436296158a80d5d11baa.mp3", + "Push2Talk Start": "/assets/8b63833c8d252fedba6b9c4f2517c705.mp3", + "Push2Talk Stop": "/assets/74ab980d6890a0fa6aa0336182f9f620.mp3", + "Robot Man Voice": "/assets/66598bea6e59eb8acdf32cf2d9d75ba9.mp3", + "Stream Ended": "/assets/4e30f98aa537854f79f49a76af822bbc.mp3", + "Stream Started": "/assets/9ca817f41727edc1b2f1bc4f1911107c.mp3", + "Stream User Joined": "/assets/5827bbf9a67c61cbb0e02ffbf434b654.mp3", + "Stream User Left": "/assets/7cdcdcbc426cc43583365a671c24b740.mp3", + "Unknown": "/assets/ae7d16bb2eea76b9b9977db0fad66658.mp3", + "Voicechat Deafen": "/assets/e4d539271704b87764dc465b1a061abd.mp3", + "Voicechat Disconnect": "/assets/7e125dc075ec6e5ae796e4c3ab83abb3.mp3", + "Voicechat Mute": "/assets/429d09ee3b86e81a75b5e06d3fb482be.mp3", + "Voicechat Reconnect": "/assets/471cfd0005b112ff857705e894bf41a6.mp3", + "Voicechat Undeafen": "/assets/5a000a0d4dff083d12a1d4fc2c7cbf66.mp3", + "Voicechat Unmute": "/assets/43805b9dd757ac4f6b9b58c1a8ee5f0d.mp3", + "Voicechat User Joined": "/assets/5dd43c946894005258d85770f0d10cff.mp3", + "Voicechat User Left": "/assets/4fcfeb2cba26459c4750e60f626cebdc.mp3", + "Voicechat User Moved": "/assets/e81d11590762728c1b811eadfa5be766.mp3" + }, + "Google": { + "Cyclist": "data:audio/mpeg;base64,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", + "Crosswalk": "data:audio/mpeg;base64,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", + "Bubble": "data:audio/mpeg;base64,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" + } + }, + "changelog": { + "currentversion": "3.4.5" + }, + "choices": { + "message1": { + "category": "Google", + "focus": null, + "mute": true, + "song": "Cyclist", + "src": "data:audio/mpeg;base64,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", + "volume": 100 + }, + "dm": { + "category": "Discord", + "focus": true, + "mute": true, + "song": "New Chatmessage 3", + "src": "/assets/53ce6a92d3c233e8b4ac529d34d374e4.mp3", + "volume": 100 + }, + "mentioned": { + "category": "Google", + "focus": true, + "mute": true, + "song": "Crosswalk", + "src": "data:audio/mpeg;base64,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", + "volume": 100 + }, + "role": { + "category": "Default", + "focus": true, + "mute": true, + "song": "You wouldn't believe", + "src": "https://notificationsounds.com/soundfiles/087408522c31eeb1f982bc0eaf81d35f/file-sounds-949-you-wouldnt-believe.wav", + "volume": 100 + }, + "everyone": { + "category": "Google", + "focus": true, + "mute": true, + "song": "Crosswalk", + "src": "data:audio/mpeg;base64,T2dnUwACAAAAAAAAAACKvWM5AAAAAPdrL7YBHgF2b3JiaXMAAAAAAYC7AAAAAAAAgDgBAAAAAAC4AU9nZ1MAAAAAAAAAAAAAir1jOQEAAAA6LNkzDqf///////////////+BA3ZvcmJpczUAAABYaXBoLk9yZyBsaWJWb3JiaXMgSSAyMDE4MDMxNiAoTm93IDEwMCUgZmV3ZXIgc2hlbGxzKQIAAAASAAAAQU5EUk9JRF9MT09QPWZhbHNlSAAAAFRJVExFPWFuZHJvaWQucmVzb3VyY2U6Ly9jb20uZ29vZ2xlLmFuZHJvaWQuc291bmRwaWNrZXIvc3RyaW5nL2Nyb3Nzd2FsawEFdm9yYmlzIkJDVgEAQAAAJHMYKkalcxaEEBpCUBnjHELOa+wZQkwRghwyTFvLJXOQIaSgQohbKIHQkFUAAEAAAIdBeBSEikEIIYQlPViSgyc9CCGEiDl4FIRpQQghhBBCCCGEEEIIIYRFOWiSgydBCB2E4zA4DIPlOPgchEU5WBCDJ0HoIIQPQriag6w5CCGEJDVIUIMGOegchMIsKIqCxDC4FoQENSiMguQwyNSDC0KImoNJNfgahGdBeBaEaUEIIYQkQUiQgwZByBiERkFYkoMGObgUhMtBqBqEKjkIH4QgNGQVAJAAAKCiKIqiKAoQGrIKAMgAABBAURTHcRzJkRzJsRwLCA1ZBQAAAQAIAACgSIqkSI7kSJIkWZIlWZIlWZLmiaosy7Isy7IsyzIQGrIKAEgAAFBRDEVxFAcIDVkFAGQAAAigOIqlWIqlaIrniI4IhIasAgCAAAAEAAAQNENTPEeURM9UVde2bdu2bdu2bdu2bdu2bVuWZRkIDVkFAEAAABDSaWapBogwAxkGQkNWAQAIAACAEYowxIDQkFUAAEAAAIAYSg6iCa0535zjoFkOmkqxOR2cSLV5kpuKuTnnnHPOyeacMc4555yinFkMmgmtOeecxKBZCpoJrTnnnCexedCaKq0555xxzulgnBHGOeecJq15kJqNtTnnnAWtaY6aS7E555xIuXlSm0u1Oeecc84555xzzjnnnOrF6RycE84555yovbmWm9DFOeecT8bp3pwQzjnnnHPOOeecc84555wgNGQVAAAEAEAQho1h3CkI0udoIEYRYhoy6UH36DAJGoOcQurR6GiklDoIJZVxUkonCA1ZBQAAAgBACCGFFFJIIYUUUkghhRRiiCGGGHLKKaeggkoqqaiijDLLLLPMMssss8w67KyzDjsMMcQQQyutxFJTbTXWWGvuOeeag7RWWmuttVJKKaWUUgpCQ1YBACAAAARCBhlkkFFIIYUUYogpp5xyCiqogNCQVQAAIACAAAAAAE/yHNERHdERHdERHdERHdHxHM8RJVESJVESLdMyNdNTRVV1ZdeWdVm3fVvYhV33fd33fd34dWFYlmVZlmVZlmVZlmVZlmVZliA0ZBUAAAIAACCEEEJIIYUUUkgpxhhzzDnoJJQQCA1ZBQAAAgAIAAAAcBRHcRzJkRxJsiRL0iTN0ixP8zRPEz1RFEXTNFXRFV1RN21RNmXTNV1TNl1VVm1Xlm1btnXbl2Xb933f933f933f933f931dB0JDVgEAEgAAOpIjKZIiKZLjOI4kSUBoyCoAQAYAQAAAiuIojuM4kiRJkiVpkmd5lqiZmumZniqqQGjIKgAAEABAAAAAAAAAiqZ4iql4iqh4juiIkmiZlqipmivKpuy6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6rguEhqwCACQAAHQkR3IkR1IkRVIkR3KA0JBVAIAMAIAAABzDMSRFcizL0jRP8zRPEz3REz3TU0VXdIHQkFUAACAAgAAAAAAAAAzJsBTL0RxNEiXVUi1VUy3VUkXVU1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU3TNE0TCA1ZCQAAAQDQWnPMrZeOQeisl8gopKDXTjnmpNfMKIKc5xAxY5jHUjFDDMaWQYSUBUJDVgQAUQAAgDHIMcQccs5J6iRFzjkqHaXGOUepo9RRSrGmWjtKpbZUa+Oco9RRyiilWkurHaVUa6qxAACAAAcAgAALodCQFQFAFAAAgQxSCimFlGLOKeeQUso55hxiijmnnGPOOSidlMo5J52TEimlnGPOKeeclM5J5pyT0kkoAAAgwAEAIMBCKDRkRQAQJwDgcBxNkzRNFCVNE0VPFF3XE0XVlTTNNDVRVFVNFE3VVFVZFk1VliVNM01NFFVTE0VVFVVTlk1VtWXPNG3ZVFXdFlXVtmVb9n1XlnXdM03ZFlXVtk1VtXVXlnVdtm3dlzTNNDVRVFVNFFXXVFXbNlXVtjVRdF1RVWVZVFVZdl1Z11VX1n1NFFXVU03ZFVVVllXZ1WVVlnVfdFXdVl3Z11VZ1n3b1oVf1n3CqKq6bsqurquyrPuyLvu67euUSdNMUxNFVdVEUVVNV7VtU3VtWxNF1xVV1ZZFU3VlVZZ9X3Vl2ddE0XVFVZVlUVVlWZVlXXdlV7dFVdVtVXZ933RdXZd1XVhmW/eF03V1XZVl31dlWfdlXcfWdd/3TNO2TdfVddNVdd/WdeWZbdv4RVXVdVWWhV+VZd/XheF5bt0XnlFVdd2UXV9XZVkXbl832r5uPK9tY9s+sq8jDEe+sCxd2za6vk2Ydd3oG0PhN4Y007Rt01V13XRdX5d13WjrulBUVV1XZdn3VVf2fVv3heH2fd8YVdf3VVkWhtWWnWH3faXuC5VVtoXf1nXnmG1dWH7j6Py+MnR1W2jrurHMvq48u3F0hj4CAAAGHAAAAkwoA4WGrAgA4gQAGIScQ0xBiBSDEEJIKYSQUsQYhMw5KRlzUkIpqYVSUosYg5A5JiVzTkoooaVQSkuhhNZCKbGFUlpsrdWaWos1hNJaKKW1UEqLqaUaW2s1RoxByJyTkjknpZTSWiiltcw5Kp2DlDoIKaWUWiwpxVg5JyWDjkoHIaWSSkwlpRhDKrGVlGIsKcXYWmy5xZhzKKXFkkpsJaVYW0w5thhzjhiDkDknJXNOSiiltVJSa5VzUjoIKWUOSiopxVhKSjFzTkoHIaUOQkolpRhTSrGFUmIrKdVYSmqxxZhzSzHWUFKLJaUYS0oxthhzbrHl1kFoLaQSYyglxhZjrq21GkMpsZWUYiwp1RZjrb3FmHMoJcaSSo0lpVhbjbnGGHNOseWaWqy5xdhrbbn1mnPQqbVaU0y5thhzjrkFWXPuvYPQWiilxVBKjK21WluMOYdSYisp1VhKirXFmHNrsfZQSowlpVhLSjW2GGuONfaaWqu1xZhrarHmmnPvMebYU2s1txhrTrHlWnPuvebWYwEAAAMOAAABJpSBQkNWAgBRAAAEIUoxBqFBiDHnpDQIMeaclIox5yCkUjHmHIRSMucglJJS5hyEUlIKpaSSUmuhlFJSaq0AAIACBwCAABs0JRYHKDRkJQCQCgBgcBzL8jxRNFXZdizJ80TRNFXVth3L8jxRNE1VtW3L80TRNFXVdXXd8jxRNFVVdV1d90RRNVXVdWVZ9z1RNFVVdV1Z9n3TVFXVdWVZtoVfNFVXdV1ZlmXfWF3VdWVZtnVbGFbVdV1Zlm1bN4Zb13Xd94VhOTq3buu67/vC8TvHAADwBAcAoAIbVkc4KRoLLDRkJQCQAQBAGIOQQUghgxBSSCGlEFJKCQAAGHAAAAgwoQwUGrISAIgCAAAIkVJKKY2UUkoppZFSSimllBJCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCAUA+E84APg/2KApsThAoSErAYBwAADAGKWYcgw6CSk1jDkGoZSUUmqtYYwxCKWk1FpLlXMQSkmptdhirJyDUFJKrcUaYwchpdZarLHWmjsIKaUWa6w52BxKaS3GWHPOvfeQUmsx1lpz772X1mKsNefcgxDCtBRjrrn24HvvKbZaa809+CCEULHVWnPwQQghhIsx99yD8D0IIVyMOecehPDBB2EAAHeDAwBEgo0zrCSdFY4GFxqyEgAICQAgEGKKMeecgxBCCJFSjDnnHIQQQiglUoox55yDDkIIJWSMOecchBBCKKWUjDHnnIMQQgmllJI55xyEEEIopZRSMueggxBCCaWUUkrnHIQQQgillFJK6aCDEEIJpZRSSikhhBBCCaWUUkopJYQQQgmllFJKKaWEEEoopZRSSimllBBCKaWUUkoppZQSQiillFJKKaWUkkIppZRSSimllFJSKKWUUkoppZRSSgmllFJKKaWUlFJJBQAAHDgAAAQYQScZVRZhowkXHoBCQ1YCAEAAABTEVlOJnUHMMWepIQgxqKlCSimGMUPKIKYpUwohhSFziiECocVWS8UAAAAQBAAICAkAMEBQMAMADA4QPgdBJ0BwtAEACEJkhkg0LASHB5UAETEVACQmKOQCQIXFRdrFBXQZ4IIu7joQQhCCEMTiAApIwMEJNzzxhifc4ASdolIHAQAAAABgAAAPAADHBRAR0RxGhsYGR4fHB0hIAAAAAAC4AMAHAMAhAkRENIeRobHB0eHxARISAAAAAAAAAAAABAQEAAAAAAACAAAABARPZ2dTAABAaQAAAAAAAIq9YzkCAAAA7Mjily8oKZWZqqazGx4hICUnKZKQp6urHB4iHyYoKZeZqKenHR8gHicpKoVNVlpxgVZWVtzQT6lK3aY6hCTxrHzcbybLBae+H/7gvJlaHnnA98/6j1Sv9n+OfwEU7bNXZ8BVfy6ac/Y+A5h8v31eg9VEbTIrauvfV89Dc8XTdy48ZZzsADpX3YyPzaRvfYqQXhc5t1nf7iBujNuTY9zcntyZTRecoJOd59kMkMIqCAQDwMO31ug7xu12iZE0ab6U7O8YtLduHN5squlD43r7dO8dherqF1XVV79dXdUZCRV75CL0ZadLmkDM/lj99myzAACAswAAQK21Hn7/h29xNxgAwLx83r19+u581ztg6N05JCiATHQkFpAmPrY8b/0HENCgNjyAiNzU6JjADERbckaCCAYo5cxc5gj+ZM7Xz7bsg+Cj65+6/mHb6lpc9osD9+FmYDDoIzeBo3rXUD1P87JlgSitkjivR+7E9EltTxlZXbI/903RLpJuaZFsNUwMZFVL431VVKC0yww+xIulXzPHIllGY7nFEst/vdlc7ZBv4eVfEZLaqivtxvRqvXNsmWACHrbkLq9v+snukudH0ibZVytuHM0gtusjM8PGRSiKYKm39SX2i1S0V5JTnh88Rn7kArcaH8cd2iTcGs75b3Rb6Yc2KWByklvLfnFOh650fSBLH3hmrLx4asqRLHjfLSqiAORW39tb2UAeoeseaPV8Hl25DwZ9XyYccnWDsK1kbOg+Q5jFYT3l/kor1Dbi3FrtCgUBdZEWtuWopjlLWG+6yMmd2bueTZ8PeAD+tbT3t94Qo7kfxfbVihuxfYjhCK0xVh1KEoNlx86+tneP+VU/1Wrs30/R90WrHtrBHKA7rZ7cXnmp+7zlaHw5nu3CNzATo6pX3Wm/0+rfmkzytlQviVfo5Io/f4hw8ODka2US4vaeWznYxBpJvh+zEy15KmnSdyqjO+9beAn1j/Q3E8QxylcqbIKnu8Jn9IudyNCOJqhp042QpRVV1tltx9I7YwFoFrbkbq+v+chcgp+PYJt7NePZo7e8Mo6e2KxXTQCQGWvPY0EES8Myk9zGaoJN07VN+zdt7wRODWVfvmfWNzGa+edsjmiT8pMKiB3K2/v+XogryJp1OJKwYwQG5Ix3t7XaD81ut2vIALCN337IMggBUeFo+88YxizPsVXLOqnJHPbX3nfCm66YfuaAZop3ll1YvUPrcdWTyCU5N7Wu7wgj4nIriqer1L9uhArfCnfpDv4aBQCE0JhefPA9mQ0gYO/pQiZ0YlPym/Wr9F2vuwJ80K5/ZfrhNOPgQMNolnDXKNps+hiHBARh+xlhlQaEzpWFUrBYV3sONoUHANCY1ZtpjKeACjeKHlZsIaZUnAGE0FZ/J1w49RhI4ZwA/MKXoxpfrCo4i+b8fCM3pZ4SFoTQU6tK/77J0HXqTECSY1DtkYpSwY1+xSb1+txwDbF1EeoJ4gE87VNPD2betGWtawrafs8bhg1WTg3aP279KCfHWqmvBzk5fK6ONw/87lPlM+d/d3OPaqcAuGKAZEsAAF/e7DVREBrV/7sEYyDx2FjvQRfATxpH3Yzbj379MzvkdH9J/QSu+bRHHjeMMYzbn5pMY6SCpAApBhHBAFoLJt6cLJ8vx/j87f/eWtKQr08aWF1dXV39p/90Ffqq5ptVFJaWTfZHS2bZbCZLy9XVL6UaAABwVisvb6mvm+au1XsHAMhKPbx+szE97ggAf0cHL1lVtR2AhXOoAhOIMwqISUzT10zF9sICPracH9PPHmTgNS27pjhgt2C0JSEArMTef9m4bnQFf8BVH27MasqhFp0MhC9AcDre7viCTMj9ZackGTiBgJqIs3R+bMdagTYa0w1a5Hk6f3PXm5Gp9nE3Cg4isJtvFUIfGuwYEFewVr65jVN8v/oIdsDFRGrMdYjNoVvHYYuviGK3N8J82fsC7LfeTBWTytRNHrbkj6e+lsiWvH9r2nT214obWnZb9WbHDBsjlCCQhvZ/lawttbok52g92OqHn8GYbaudWsp6tJYF0KiHkf7PSL/8TiT+1Ch+TQTIbDfgujX3ne4yrpWF2KY1UDhGSwCALPNDPZIByGzLiQaYZkHcX3j9zAYhwrFjgsqjkKFeD3sNhilpVt48+VmtB5ZUQqj1ut/VN4/eN8PfNUMaRLkuRRmhuNUtswEetlTOp94GMornozg82asVj9hOcxuzYEPPADSurkZbksCyk85bcp7Eo/djT+160CLW3GC9Q2VEWq1T1StS+tqrxZdTH3z7vPNaRj9d4oClUT7FIO3Z7mSizaQbEBkGim6QoCSX9mXzvBlifk87tsqLCNJSyc17bV3E7nyDVcbmGrVa4q6S2HxlQwOv3efFNWhbZ6/bdgftqYm29oY/7CrZ/te/CCBKdfBkKAAWtuSez5/9mCv6vpIl+2tKzmZ9ZD32ON/TerOWEoDMWEtQgoAu86bJYr+xSrL4ev/W5rWTKpfucpPOT5mCoFGdR7L8ciQLkk7KBxzZNLgEp+E1o1WWMsJr/ySTSoUgXhRqDwAo/YfNI7mQovGfXnPZZas7+pVmk3SC9qdsLFQMPgnbV0z3u1upkBaybTvUjL5NpVCZOHHJCqjY6qNGIBlR7clxGzEnyK1BFBmE0BRdmF6XGQAcbg99Z3cj9POSY+MmtaYrY7oEjM4uf6mkhndus0nDMXotc4ju6+0fBwyZCBxbDTklhNCzjM5oDyA8AMASsC/gJU1eSQ3OvSTJmSluh/Pep5DuDITQFn/h9sKzImj6AUCT9tFkx9UioCjHe0lAjBKEGhOM0FOMSl7XN+x7YQnQLXfxM3N1vRGaLvO7B8eHyYk+e9vZ2w7sozTvs5WeGN8P3aztsKcEoPZkzt/t/MTLVeRe++Hzs13m2YyVdphmaAD87rPiY4X44xgA0DEAugAAIPEZgAj6fQ0mw382Q0eSGUcTX0+Pz1WAH/o23cyvX/r1f+5I6fNKTkPNbHcUz0aPvH8V1+3BGANgBqngBD30GACQchARDKCt6SlGwvvrjFu49n2vCe8YHmOMkbPllJfxCLGahEVTecnq6urqJzAsqwlLy1o+HzwfGXj/WF39J1tkAQBwesIDAJD1+M2TPt85Hw4A4Owc/p9stLrKQqW6eqsBQB/Kfg1Z7TUIP4/IRQE+tjwe/UsgKCbUjCPMGIOUJQQDnOvTPs77Ts9/wsh9u13vSj60SuR877FLzPpM1Ufa2NeEX/XdGi4hr4JrLyGdQJWLTMohssDKq65r3rG7nlnFo6wsJCB/u0kNruBNkR6chni8nNpWWYwq7tDNuUHmNks3VKxeQi7NLm0qosbEfOQn/8d/Kb6y6I5QhlT/2dRV5pzFRRazAWgetiSPx0/+BJ6fzTrr14yHoDnE0XF0HB0JIEtsFJ1FGUjDtfnUkdhlSpJ0r5icNVwoDaXBMU02jpHJu1vLct1iWQ7FXU7ieQVK2MRMZpyrxq4wTQ6UPs/cmsrd2/g7mT20GgRZbg+2scsAuMi5t/PV6+ezedzbFblZQ+xKDnW0BYY1i1zfHO6IztCA9SiMqXQds4Lu0TA+PiWF5soGilgJXDVX0rgHnQcetrSOt76SBMP9KPb/GpMjVtrQaUEkAD1jHUoSgkXnnwHv+S54suiuSlW1VbCxCwdqR5XpPD9KdzpcSPfqa+qAtS/urLXt3SKyvg/05APmOuZO4NQ49s4mnWxqfXvdojthhECj7Z97/8R5pk3R3PI3ZSN22CuzGO79pplWqmzVAeK6qp7kkMzrV7gf/pysIvPXy90JjbRUbAcrqSuxB0dvJbX0TPQFABa25O+vr/jZWPl5BdusrSnZbMbRkat4GXFkjI0oSgjYZY4JLv8S49r1590Pj5+fb7ScZVzGv9DjemxDf+CtZU85T6WAkOJQPsD1L1QmcfIzavzPX59P2jLn6G8/OSrb19DKBQDl+lGNnSow5FYcyng5D6T9DEmR9yaHT20Bg9Gql9wSLxPfsd/rgws4clJJkOLq3ojp170CUGEQ/1AP/kfxdxtY1KEDfNDcU3R6kUoLOOC+rbaG91asiQx0E2UpP/Cg9wCE0LMKNZgvvPMIajQ480Zsibb4jx8PCeDYP/5HyoMMhM6zVZV5ogAasAToJHW3oUCJK9PEZouAnr71vtlkLAWE0Ba/u/DNiw7hA4DL+cd4OmJEHQD3vdmu5lYcYgHs2mtkNqOncXRQM7b05n/+q3Yy215YT/4dL3wAAfw/bpPst3pikgA07YuW2O/vYtAZEcRNRQIbBxx5nCYNB/HaJFlLCMNz4+w0caYCIcdbCfTu36C/5+6rIZ0XQOkDRDAgfxXMcl4DMFuG6wFARVBlZH79rQQgeEbBCLqJZu/lcwQj6Z7//WP1SxzqgnAUUAbeAQDAFBgjGWOMBIJ0BwAAEgEgBfKOMcYIAADAXLJpU6DWfLYmAECMMUagn+9O37399sazMTq8fvOkrz+eDvLQOK618u0W23zNVldVawXy8KPee++9fyRQAgAAf1peNgEAnMF6dfXbVQT9RAUAAAB+WT5v6beuQQS/tf/+68tbT4rgAYBJAEDyBQAAAIB0BwAAAgApAACcQwFgbQAAAKBNAAAAAIOpZhSN43x+iKuk8ywECqqAhQliCgAAAP4oXm8rT7/2IvgIP/6zb5rsUUa/ACC/AwAAUAKkOwAAEABIgRsBAAAgFgAAAADbzi/huklNK/jLOfYTVhp+DUSfR2GVBQA+pwAAgFzeYxSMg+YMAAAAfvid7gdPv/rAr/T1r8MeYfQrANMBAMkXAAAAA0hfAQAgAFJAAUBWAwC7AQAAwJkCAAAAbOJ7moph4KPrPrivG+29HU313NLMp7juFsCinAyDDnBO8CYAAAAA3rftt4XdXn7gM/35j26yRxlaFg2gAugXAEByAQCAKgjSDQAgIBFwa4y0qw21eXpwFYC9AgBrBAAAgNkAAAAADNBsbh8jn8/96f2I2ZimSDMGPJDw8qBfPWYHijM6gEVjIktxsac7vJjogAbwRwAAAACel42PK7fdfJiW+faf6MgeZWzH3BsDElQAJgEAyN8AAFwFMbkAABRBCvzp2zouFdaS5hYA0N8CgDcAAAC4CgAAAExpah1fOgfHhOvfHxfVrI9rWyIGpCwXLO9lh6DXkXutr8O7rAYwG6Bfli0AL4F0uuFZDmQZ+kgPCh3gVwAAAAC+h02PF++zeXCs4e1/PzU8e3dC8ApADQBAPgcAAAiC9AUAAAEGUgAAuAAARAAAALgpAAAAQB8aByDrt2c3U18Wkj6RDBwWV+44SwKwrziz0IC/AAAAAL53Tc8n37N9cCzh7X99m+0djCYAMAMAkhcAAAgEQfpKAAAEghQAANwBgNwAAADgFgAAAAC06V9FFLB9vgxNz6/7WRkPYIFCo3Q6ALAND8AGgF8AAAAAfmdNjyf3s33wtKS3/6HN9i4CcNAAJQAguQUAAIEA6U4AAMFACgAA8VQASCMAAADMrgAAAIDUYzCA6Q+d7h1flBELAG44AY0+aQCgMbvTJIDlOwAAAABPZ2dTAABA9QAAAAAAAIq9YzkDAAAAvce3bCNcWVxgZGJlaWx1bnB1d3t8fHZ/dn+Bf4J/f4GHhIyGj4WRkV5Xjc8H9z28eFvL2/9dTc7eRQgOGrADAIB8CgAAYQGkOwEARJACAACrAsAnAAAAgFUAAACgn5xVgYn3no46gXq1W0QHgIUwwvZ4zGIGoLm3XQelN809gBMAAAAAPldNjwf3vn3xtoav/3569i5C8ABAAQBIJgAAIMDASHcJABAQpAAA0FwAYEYAAAAoowAAAICYAvFKY04Fario/UYJALjhWfy3KBwB0AAqN4aIRcECzQsAAABeR43PF9+zefDVp7f/S3v27gTgAUDPAIDkFAAAAsFIKwEAiCAFAIA7BQB6AwAAgCkKAAAAyAUAM01Y3B8aTOlz9gA4gwqdu7ZSmCSmBzwA10rJYsNOmAAxDAAAAD43Lc8H3zu+mNb29r++NZm9i5AYAPQBAEgmAAAgyQBpJQCACFIAADi3AkApAAAAIAAAAADULaOyAeBuJp4L57q4UgCgA0DYvVxJ7ZS9McQimbNAMzRqcy09gEo4CQAAAB4nLc873zM+uK3l6/9/a3L2LgIDGmAAAJISAACCwUgrAQCIIAUAgPNXALgCAAAAVwAAAACgk/Niybqn8W6Km1l9Yq4GAEzkpU62mAoUzj5BB52U2oomd1Ij63rdHpEAbxsAAAAeJy3PB9+zfXEs5el//bX2LkIvAFADAJIKAAAIBiO9CQBABCkAANwLABwKAAAAnhUAAAAAsnsJGY/69ym/83S/w6sDAOjsDCPbBDnJWkyaniYAA4DI4En1uOGTQJj8CgAAAP4mTc8n7715cSzp6X+lrb2DETwAMAMAkkoAAAiBkd4EAJCJAAAwCwAwAgAAAEQAAABgpRx5UHF2RpKPdVZs9LZhAcAEF/NFy5os7VeYl/fQKMCkFDD/7txuCL9RFp5Ex98BAAAA/hYtzzvfs3lxLO3tf3MNZ+8gQgQNUAkASAoAAAgLbLIJAEAmAgBAFAWApgAAAFABAAAAWPpg9zhXsuqjw/AqzI8QSxkAdDhQrm3HWaYGTtQjQAHoQNc7zPWaapXs04gHdAoAwS8AAAAAvgb1zwfPPbyYlvb0j8Vl8fYOJuAkgL0AAEkJAABJRtKbAABkIgAA8AEAUwAAAOAmCgAAAEwHZYa8KT7fi9hwO05eADA9PfukfWS9VgMze6H3HWkoeHig0WmMkHf1JmBCjND9/jUPCnwHAAAAvvbUrzffk714yvfbf12OZ+8AQgwAFACA5AQAQBBOHgAAZCIAAIQuAOAKAAAAMwIAAAD8SYzQkTXzMLfJCi9SBwBA91o4WJOnkPiUWi7bbv4WI0Nn2wxrHAomYNLolvkb5xnqYs/C791JDssfktBpy4kKAAAAvvbUrzffm1585fvtP06Os3cQkgOAXgBAMgkAgA4E6QUAQCYCAIB3AAABAACA6wAAAABgy/eluiDez1pBpM0ezuMpAAAmdLhbXomJlh5OpTXBkhumoR4RCV6fii7WOKTMkJmbDw2KGQIEbAoAAAB+5pSvN8+bXnzlc/uPlmPtUUJyAKAEACQnAAACg5FeAAABiQAAEFUBQBUAAABuAgAAAABbi4/Oztzi6+gxiIes6+lOCgCgM/FPkWC2UF+yYyWvORhFgatxyXaM8H2mmjppciWBZlRDL0Xg0YkYAEAAXtaU7xfPbX9M+dz+uznNHgX0A4AdAADkAQAwDklvAACQiQAAgAAAWQAAAPAbAQAAALCxeBWTaphnZrgOX7t99s9EAUAtdXtqCWZvcHy5OtGe10BOevtqK7bS/TCQQGp8b3e5NkqxO+c6eooDaRyAThMAgAAAXsbUrxfvnX7c6tn+05Nl7VEggwQgAADJKwCAIhjpDQAAIgEAAFoiAJA7AAAAlAsAAAAASGbOE9sIkuxl3Tavxn2kncuHPKcJgA7tE8eVuhmzUTLDMCl+A4qTt9S+YuZ2MImO30VgigX1UmDe/IlCN00Fv/9VLAA+xmSvN89jf9zic/tv5Wh7BMggAfQ9AAD5BgBABUZ6AwCATAQAgDoAgO4AAACUJwIAAAAg0mhRyp+eg4g8Y2gK5XxdvTHWcwLICLklaSS1++fpgyeK2n8SUa2Jip02C24HWEzm+CH4+hypmBI5qjSXA8fQG5qOFwCAAQAexmTPN89nP9ziPf0H5aj2KGMJAFQCAJDvAQDgwskGAAAhEQAA1BUAQgEAACjTCAAAABCi+Fr6My8OGofytniybSU1yH/e0gag0aY2yjThXGXhOv0SGUlxqGALqZ8VZx3LA/hOJ17H6Xu1r21f3PfkkgqewBDgEx8AAAAAPrbUrxffbX885Xv6TzQntUeADB8A+QYAYAQ2UQAAkImAOAAAAlBWAAAAAEDyeW4ldt+miRyfD6U+2N5cW1WOIwAAANfvAAC7n6ZUjOLOD4w9YJMQO0YtuuA7K/5b67nOiQBa16hcR0W24Zvq17NTckGqaag92MkjAABgAN61lM/HfWYQt3wP/+lrDmQPgQwiAPI9AAAKJwoAADIB6AUAAACoAAAAAIDwrP3BFN441LTjDzJDt/ds6ne50gAAyIcAAC3CmqvbkL6/pDWYp7da+92Zw06mVx9IAMidjDYqqdyGW94yXjZEX7/C1mEf828BAAD+tTTPB/dJP5r83P77yZHsIeAgATgAAMkFAJDDiQAAxAgAAHMqAGQHAABQphEAAACA6VBy8txaT5fDdctp+pyc1IQ04soIAL5VIbwRykbCu2dUFUyqnvzO3Wnu+oSdRqRuZ4EHTKYUS8Xv+hDgl4SfpsUec9uc0UbXM/7gJGEC/rXUz8d7WYgj38N/Opd2IfFJABIAgPwAAMQ86QAAIBMAAOAeCgBfAQCAA1cEAAAAsCHyDVXG3CbKk61D6VEFEzgtQEUSfvcmElwGZ02MPWPDftoexZBF+mPDkPHAZKDM+d+JvPmc5M41DyJqCTFizQITvAAAAB62jM8b78l+bPU9/IcnB7IHhMMHQL4AAEY86QAAIBPApgMAAA4FAAAAAAx0nkElyjnmYYXt9/jt66q0U8GPaMYEAAB8XgEAI9lKdYfBJHtv2ySsoXl32nL8uzfOxN2Nba1Z8cCEBetbJEkWYbAzTTRB5NKhxYAej5XMmg0ACAD+tfTPG/dKP7b8nP7TebQLcAQACgBA8gAATMQqZAQDAKQEAHoBAIiDA5Ci66BIrgfN8946fzzPZ3tbK7Qm2827AwBYovqxnwuPI0D6ZGZIuG19u88nsHpE9PkgQgf9I3hUtvAzAgAAAKCgFM9YFJFnGfFaljZQkrSBuAEd/QzQMQEetnSvJ++xH7Z4mv8G9kBxkAD6AQAkPwMAqHjSAACGCAYAeAYA/ggA4AWAARwAAAAGcH64NQ3rTNB+e1bTnd5bDlacig6A+PPbY8USLhbiXpzWHtY1v3LfmkbchV7xVo4VfEnCmqzv1gOR1ewobIUlDTOQAAyD7FwV1+StAWACHrZMz8tnGURQn9v/PB7rkUgJACQAIHkBABjKkwYAQDEAAGhWADAFAEDhDg0HAFAAAAYz/ftfLGhvalzdvMS2g9ZuxPJ0FAA77gPL8C9FYjLJs+rkPUmLuh9ry2xGlYPH2KVmBE0XJOxkhW7ftmBosQh8GdB5IHcmQi8rnKx1OFNgAf61NM/7+0iILd/bfxrVSEYAoAYAJB8AABKcNADAIIIBALcAAImA4gAUggIAgAIAZC2J+nGW73P8FQbR7we2E78Z0soAAHg3V1qvb1ZOFVBOBlFCaBC2typrGeMvMyvSZDe1raChR9E5GUfaZkBGr3cmlj+F9/Q9gsZfdU9BARbetTSPO3uXP251T/9p1BPj8AGSbwAAOZw0AEBi4GiAOyAIrlgHAABQAFCx1qwgiK0tbbj5CVZtP/feZ6FW1gAAAPv/AQATsLeQo4ciWu5SdtBfOi/3LBvR431XhzjZIQtIPBOj0OeTGfuJ/TlqsiqVK7RaLktQ4nzpT9uZpRcA/rU0zyf3ej40D8V/nxWuemQsJAAJAEFSAwAi5SpkBAMAvBQAogIA0ABAcnzLs0L8lK27T42Tc/9+25h6zcs7AGj6vEgJRcBlLhYwi5BXVjckU5UGVvikpXgg1sza0Hqtlee18jNqAIABALohz00flzfNAAVeG3dXHPAdN989BKAB3rU0jwf3ej9ML8N/2RJXPRMWHwTJAQBIcVVMAAoAABzxBpC0PMALD6+dbioZxtwbQd/Su+3QOQYAAPj8HgBAdAPbTZiiTnZwNzuRdJCL7UZvc8aJDSu8jQrij5rc624QofBHLDSHqAEGAABwUOFmB+oQiZjO7+1pfJdaLpw2DfefGlPyzgIA/rWMjxv3eX4Mn6b/FEsc9cQ4AgABAEFSAwBM5CpkBACAmwgACACAgAAAIMrMYZO6NyKm7zTN9nRrrnYNZm/EA+Bp6jwQ7qhg82HVRk+z4iPyvL2yj+OHkqoTAtfskzTLDUMjjLPHTAAAAwDl3M38/vuxmtK3yYkcI91jGq42C6B8mh4CHrZMzxvv/XxoPuLwn2pM69G4AWgYAYAAgCA5AABGTAooBANAANpkhwQAmBcAyAIAqgCi31v5m97DQ6jeb3s/L9fCBIDcLDu0Jwnro7EdO5Xa0IYwqYq8sSvm7HNxelaQ+xLm98jcQ9aI3mVMEAAJwKbiJEqTOZUvnY0z15ZFsh+KWeDn2pmmRkcDIAH+tYyPO/faP2wPw78V1cKknhSLD5IkNQDAjrAKhcCDAgAAHADEYeIeM3posxTdRVtR311AtuEELAAA+wMAAOgcBrXE7QiMG1ilfF+JnpIaLqIdtRZlh3xAPGKT7UFMFZMevN3/kFEAAIChuKXVOPLjeie/cxMi3zQiJVZn0xKSfY72jpgLEv61jM87d90vjkeY/isWJvVYHAGABIAEkgQAhBGrUAwGANRWAPgBABShcGCQgHp+sL/oJTwf3pyz/rmfJYV2fC84BYC9mLMPKq79CCygCLYeqgQXPdYtzTuQTWrBYbt9O3oEZjeL1mc6Ee4JRI0FCQCI1yBOS2+kbFskSQ6f1T9yoXmESWT40octQy5NxAMCHrYsz/Mzb4jp0Yb/roVIreiTQZIkAACGsSrIQKIAEB0XXGGdJ7dv6gxyenPzfrj6NQHUN6qFBQAA5hQAAOCa4N9ZOaXuVr0ujP5IefwaJxApIZKbFzd5hygG9BgnQWlqXFwxB3ACAKzKLsapnB+MDL9sLL99L5VDslSCSoaG1b4tz1zwAP61TI8rd39/DK/Y/FtpWYLWjIMEYAaAJEkCAFKulhAMAJgWAEgFKAAoHABmaPW988+rq3xmN5/nZq70on3f60pBzx5q231pRcGr+EoTqkSgBiHDbZSprIoaka6WvfDjdrbi5bHv4AssupkdcsUkSQAAsGoz+gi21HsSJX4dWZebOlJW1Ehgd2HbVHKj7jXMBTwetkzPO895XzRXbr7+tzCpFX3QACUAJJAEAEBSVYwAAKBWAcCAUwAAcwK0e1rL1Q73A7r0S/B+H7Sn5+IzkoEMGv3HsmzzGiVmu5K0ONmWhZPVp3c/auT2bsXVvWX0fyeyrb6fYvN6RBaJpRTn1ewaAXiWTRZ3OUADd1u7fouUuNZsqq8gWsjrI65Ec2C7C7IAT2dnUwAAQGUBAAAAAACKvWM5BAAAAE+pIT4ci5aPj5COj5SPkJSWk5aXj5iVlJecmJ2cmZuanh62rM8L77p/BA/N/4WFsFYsZJAEnQCAaKuGyEAEwIESUQAAh/OmT5stzOSqh/zbkDhf8RncsQALAMDnFwCADePhqf5r8xcrM28Vq0n85H23Ny22r3/t2rz1WneDc8sd0H8ff1Uo5oDF2XyFJJVJilI4EA/s7Bn50oS+sqYNCulFeSKc0/XOFhilpy3+tez3g2c+P5JXHf7HFia14ggAFACQADoAAGS5WoxgAIB0AYCnOACARBwSyA7i1xWt8Sv64EZQ3tf98Yy3H88KLisgtNIm23Pfb+DFBhJyxLNzweDoOAXjrdOR0n7xCbIDjFvkOSIVciBr6konlB9DB2eCp46ULTsjirrlLH/puZOtCW//zB7oj0zp8i4dWnIwZiQPpQDetaz3Lft6fnAZvv1r4atmHAFALwAkiUmNJUYwAMABAIBknL1T8cst2+c6zWV/zNa4vQdXkTTsOg8AGZoBMnvPspwqAADoS0MTfXgbiPdO7Dw+rlV8tWwWb9fTbeVg6fHvPJRDiGOhlzHvs3g2YLTsYofcizxyoxFxBGyVqSBGqNk7kX/oWn0/CMyeO4EGDx62nB4Hb+0/klds/rcsoDVjCQCUAJBAjE61GMEAgDgUAABDJuLXokbfH+ha5cvvVx5fZJpywFAbgBXIXx/Ox7dK8BAAGKBkDSm2qMiNeNctxucg0rMJbeI5L0q+t2VWnsiINb6shIt2v/vah+6VDCM0iD3zGQDZ4Qi11rVhA7+yBt9m5/3DcMZsJ57WYQF0HracHjtPv3+wav53jY+acfjg5BhlSQwGAEAZr0OeIti+kCek9waVPbs6foUOAMD1JgAAKKAeGpH3gkfWZ8eqV07FAICHbv/VPpKZTbTs/9GS3FYzrwmLns1WyUe+WxxmLDuZhwm7A9jNR+VZpvERqxIpRc/Cj09CEjObEs0nTdj5pFGVhWV7K4gEngbfTNAAHrac7zvvvH8Erzj8t4xJzVgCAAUAJCKmYxZEAABohwAADIEH5sr/Y+R1+OvcYrbLXj3BIv+CNyULQxsDe+GDQIyDxmzeNgAphiHMY1fZYiA5Vo73otfEwjImIldmS3Xb0uf2U3Ziwop5tySU448PqjqPncXIDmHxAn8tlALSiN2lZ9Z5eVBi62VOSpt4Ev617PeDuz0/oiUP3/83JrXiCAB6ASARWQwdi8EAgDsAAAAoaw1stspr3+IZ1Ffv4YeAmatwJW3XIwAAWVVgy+mP+zQ3BK8ZGCCHSbgFYU57eE0qB5EOrcX4ZX73qZTW3Cjd9m9i8Pj8n591VsCtvOYHu8l1wB9xggxKV6FFaNt29eRG9rio57XlZ8MyUdABHrYcHyfvPD+CPjRf/bQAa8YRACgB0DeRLYnBAADNCgBAYit9H8RyjoQlZ+9vxxt5mzoTNnJ5HMwALA9ZnH2My3KwgT8aADRQTVzpptfPzmdz1LxbOYjGXHLGTAvRW+/9M81RSlXD96e2zVN2PuuMV0dNuhEtimmQQVDf3O6CzaW0ABdrtLYu9yy+vg9gMlnb9j0ABf61XG4Ld18fgiUM/1VjqA2LD8lpc1WQwQBg8I29PLb8vu+x63HLOu6zqtYvx4+W9wAAXL8DAIABsnuPv8bbl8x/FgC/kTmQs0jSjhjIspptr7E4a4cbSYbAbu7ZyQIvE+IoieUErFsGqTiaUCyAYXINw+NehmaUdWkjwwhso9+ycUEiDl60cUsbXLthKYAA3rVcrjP7nD+qJU3/OwG5VswAQADA/ejYkhAAAMITAAAIbYfNxgv46y/Ubj8d86djsSO4djbRCZjP1IR2VNyw0ZqMOQXvgxIyZaGVKM8xFS+z3Tp/rofDbI7QQfvRoKzUrGYrJBuf6ugr5gDn8cSCuFygX50LqYsZXqTfrmi+bFW1pre9zxVLPYKknZPnBIIH/rVcbwt3az+C3vCfSKgZi08I3AAAqHNVCYEvaLVAcFQlBgeImUc3eq13mVd5ezwfs6lL9aiP77TRbRQAYH8YAACHbQjo2s9pZlytGHf96mrOt940WaXi7a00G3+qt9ry9sLZill4ikZ0NmPcuxceIjBjQ0p0YteItTgM4eF1qO0mHf83RXYi562iHgGc8yxfNMIHDR62PG4L74wfVW/4b0trxhEASAAB9AIAFDqxGAwAqKwAMIACkQpeQgcAZ07Xr73dm/jhvL8fvxaSKo1yWu/yFkviLhBLqyjP/m2qrFWyJhVL+fbV/WuoM1tZeT/ejlCge287Md2VmL4M9aEaEg8MAe31ybfoXlq7dCe2DXkxIvvT9lH0JlMlwS6CtktYgQi9gwW38R4CAB623N4m3tIPq0Xzv1quHRafALoBAEaKEgM4OBoBgAKwF/spsf30Yw+3+99on767rAUEHTa5SrZHAODzCACgGS71tvu62Uzfsd2bkP3vStbPruxACWaJF49nYfhRbsLaJd7r71YrlxhV2HvbCMb2MALdOWaYwqiSdH53dYEKT0juw3KnF5wRZIzPyC4wtlq+ETovAd61PC4Te8QPOs2Xv9SK5ApAAYCVBQBQ6ixLDAYAiBEAQHu5RATkAkBgRzvMoqOv3IeNhqs8N1eCIx7aAUrMOzH09+ajZBjZHgLEbyqd2m4Pue5yBcyzaCu3vWbKwN17i9i5BWyNG+jF7XLGm/3eiKzsQzMjBrDlbE5KbrYYS9jXh5/1bDvqK5BhYuo3TXMhN7cUVyQPAN61PC4je9SHIBv+A6gNSwXQCwAnR3A5ZgYDAKoDAABivDTvccHuDrkyer3ruopNw6AT3fEsoJnNaWBW9V0F9qXr5y9ckZ/KRTIsxCLzTUSqirgr5UauBIHNkfZdi7mpd2IS9PuJaR+m+PsE/Yz+c30+au7ZCfucHW/LZ5HTX6m52FcTovRA7SHTeGyyZx8lW98IhaDRAQ8etrzcJt7yD51O89+Qa8MSACgBKl7IDCECAEBfFAAAUPD0yX4Vbcw5NZHHqJK1tviJzxV1ZQL2Nkak82/lNy1CvSe2Smq5bL4GJxlqmTU4YcD69olHYo17g8wkMQeySvfJqFTkEDOBwifrprHyOm3PPhWtWVe/wjxPCa5orh+BDph7OZk54RvBb/pwEP8wWR62vF4nHvcPUVX8J1Er+kWVBgCkxIIMZjRgUcW9cFci7cr1t7PuiJx1cvbweD7S5CdVGYoASwBgTgkAsDPVIRrY0sU7a1HqKbJX3O8Znfxn8a2sUyy72m45QWdaA/+2Xa/ynY7uioEFZPVaxDMqcBxRQ8Zp/eXcyBYlmaZOxGKdwSVm3fvA1Zpj2iDDfhKltKIdUVgB6IACHra8XjtP+IeoGv4jUStuACyrqMgTQ4cAgBtiUTaccxyep4zxKk9JXav2jE/TeqT8MhbEAMD+NgCgiMDanNp7kX/05q1qd9riybz+vymAlcrJxZ6ldYLmX52jf9e+0bkf2DAq7rva2MnYK3pEsrBDOWXPu4axgd5ZCnwYvnAxldlkbaU3K6DUFACR8xMGFf2e93JxDwDetbxcOnuIH1HUPP/KNeOoAJQAY0cqoRgAAOJQAACABHzUdFf7/8q09XA/Hx8OPgell4o6mamsooXKparZ0yXF3SYCoBjDWBuTG8Vm0NETtFU9RrFBlGSoK9PYTCj6i0g4TqGmb77fbgXD77GoUXxm9iU8xSMX4xEyFdnVNyhlmm3smhRKEemE0NL/4NmyLmRuHv8JHrZ8XDuvyw+9qPngz9SMo9BMOSqIYAAoZ7Z8a99zic+Jpm14PNu112EJKRYqM5OELABwvQkALFherfB63vID811ejhVMZFeRjEfgil7ju926pkEW57A0UX6w93ZQ2cnJx/3lgWEwhNfdI2/7NhU0J8iyJbcQz6Qai+S4bScZJ8tdmrtp3pqLoQ0i3N4U318/2bsiKIIJAN61vF46O/ojiYYP/lIzjgpAAFyJHpXEAArAdQEAgEQ7axzftOur8C9TGvtBlzJwEupWj+FzNL4DbnWP2c0V5tLkEE5eSFsqT9iUsLI8xVlVbRnccm5Log+ccJjD0+l3KC94F3H5mrMgdWlIpPcOwuPge4Ien0oiobASZg3XR5U/6WG4EbF5V8JDm/SiJdMc8poKkf+wDsLVEO2MBB62fF4qj9Afnah5/qVWLAGAAGSaKAkiAAD4FQAAqR4Lu9/fnSNfd/e5f76bIfvkJyq1+r1j6sp16Hq46IeDAW+6Vd/QRhsyNVh0tTGnsJ9UDIUlc+V6zjIRNny5ANfrRRNvO1xV7uPSxsF8VP+bAusjss220O9T1/a/h0xTfy2FeBDSV/PwUBIIM7enylQTIdClsySKGZ0F/rV8bgu364deNn34T9SKGwDIKGFJJzFkAHCvopzPi7y+8Ch/da4tLc+O2bwdCk14FgD2BwCI7r0tJhzGqrxkzqGJdsxaepV+O9F/Mnb2dajKmYsQ5ALNOh3IE2XovoAuQB+ZKH3ifNimCGbbzZC7VULbDiGHLke1EqTB/yjvnpzzKWr9C3pZV6qq0TZX28l7w18pvG+ChTyyeAAiAP61fFwKd4gPK6ZP/9SKowKQgJ4uhJIQTACgJgAIAEBJgnqYGCVyaL3etgfP46EFhFAiC+HL6twwZlgwzwH3cNIQXHdsxAf5+3puxEcTJu7FIkDJvrfzk54vbeSlvklRJIZcMYswd8M6RXEEjDiMCoy4PQv0UZHCylFOkC22e3+xbT8+NsW0TxNfcmjsXr7gdplAHHNij7jsTWvgAR62fF0qj8sfq2j4+pOasVQAApBeOsNiBgCAmgAAQLhla0bIDHG+Dk53rz/pc3w4ajPIixt6VsMdt+PosB7n+lBV3ooZOSo0BgorHpo4KysaXkkXIzgliveyc2xcbO/jPfFYh2smSmVoYXebszDXUDm2lBboEzzhcYpJBTRb75zF/pYejNFN+qE+BaY8ve4hx32J2n0e7hUICR62vN86L/qDZHh+TK04SpZoJCGCAaQSSedn/fq7pf/dw2g97M4rDFDZdcsx6w2SBGD/F2CYFOkOoA+1ePK4o0h/Y5wF1sZ3gWpPjMoU7dkY0DX6cSgOQuKmODY65+fPZm1vkO2oRnFFmq8MnjOiTjtnMc98/dtNJtQcdgQvqvnM+4FIlIwIpU1Xz9SY5cXcxOzv0pa0XEWCaHgAHrb83EZetx962fChL7ViqQCUAC+jcZaEQAGAJgqgEaBHRwSmOeUXRLkx2njVX7XVFegleukVDES/wBR3O9tYm+m2rTYegvLhXjBR6kjIhlsDSJWIJRMYMlWpFYGMrswJvq2x1/EwBbRK5iG77VsLnnOOek2RzRpcsVkpSurz/Wfr7EWpF6/VvTnDuKP2MJB4bJNCrDRuHO70kB62fF8qr8oXHaaPPlArlpIZGjkxgwHQ2DDlQdn4diqZ11FMzvtlJgMzXHTFtJUEgM8jAMldbuvXIy8HVcSxZbcQtr8JI3AMUj+hWmc/IOUENrjIaS7VX+CQ8kYzhmG5wo8zy6q4e10IuvQx9b64Ez3nQKa2qjuemMwldUBklkLNueZC3S/Nal3ahCft1GxOvZU+h+y1xontdyMmT5kAT2dnUwAEAHcBAAAAAACKvWM5BQAAAIzU2MIFqJuamVX+tXxtK7ebD71o+PA+NeOoAAQgvVR0EoN1CkCaAAgAjKf38nSNf4u3ag/+XpFtEq+zjyPPsXu95V9cnFU4vyRGvULIZtAGxTu7yORUhDRxQTxlWh6olMzB41ATLTnSIa0CqEMWv8nu3s/XgwnvyD6iqRUsAz55czD9d/r6pdnqfi0D02+LESrCpcvRlQnhZd3m/sVEdhvJWAnzJ2b9a2800lpFMjsWgAcetvy4FF61Lwia56U2HE1GQy05IRgAUIoKrhN1dtPIZ/JY2kFuhzJe3+u2sdgTd+Q1IFSGn7MWwem43ZaT5sZ2xIgr8SIH4ld0VWuFUbivk60RqbK8CjYutHhQDuyhWPl15GZXAifUfWBrHec0bFE6zfCPi+fTE9dYg648GjvMqfPGwPA7Kh81ViKXyJadJMVIfmntWjCbYXwAJh62fF8qr5oPWdY8l6hmLCXDUA46RjAAm7kaHwMP07gOXzlXpNBUqlxvcl7LuQuUE4kA9qgxwGm4Btpgd4c0BUqqN3rLhLi4Fh2iHJo0R9dbrpvN5vTqrHgesjvVJNBSG9TsqYlazlTVi8hUhzH2mON53LWD+koH815UYhq2idnRioq5p2jTyIvIDXbs56Pn1Yk2tVpTtGOeAAD+tXxuR27BByhQK470Mso4MQMAGEDRVH4pyLLa2oardDJo/m/O76V8dmdDqkje1KIzMptBvZVpW14S5nnioc2YetqFDsrKeRmMw9pPsz+LMk9xTEgmqWm6mEbccg4Wl3Bra+mtqGmD5rhii5av6sqrckDVZD7LbPNwVDy3pGdt1WnYg+vJ3III5C2FmgLcA2YwFnL26zA+OwAetvzvKLz4DnhDTRGEXBSCAEMwALDpQ5V3mE8BS8svH/DxwWFSgNlawtKa8+7MLtOQqyi40uCB+fxwlhW2YIl415T49BvgzgBDgEcVJndabjX4wSYA", + "volume": 100 + }, + "here": { + "category": "Google", + "focus": true, + "mute": true, + "song": "Crosswalk", + "src": "data:audio/mpeg;base64,T2dnUwACAAAAAAAAAACKvWM5AAAAAPdrL7YBHgF2b3JiaXMAAAAAAYC7AAAAAAAAgDgBAAAAAAC4AU9nZ1MAAAAAAAAAAAAAir1jOQEAAAA6LNkzDqf///////////////+BA3ZvcmJpczUAAABYaXBoLk9yZyBsaWJWb3JiaXMgSSAyMDE4MDMxNiAoTm93IDEwMCUgZmV3ZXIgc2hlbGxzKQIAAAASAAAAQU5EUk9JRF9MT09QPWZhbHNlSAAAAFRJVExFPWFuZHJvaWQucmVzb3VyY2U6Ly9jb20uZ29vZ2xlLmFuZHJvaWQuc291bmRwaWNrZXIvc3RyaW5nL2Nyb3Nzd2FsawEFdm9yYmlzIkJDVgEAQAAAJHMYKkalcxaEEBpCUBnjHELOa+wZQkwRghwyTFvLJXOQIaSgQohbKIHQkFUAAEAAAIdBeBSEikEIIYQlPViSgyc9CCGEiDl4FIRpQQghhBBCCCGEEEIIIYRFOWiSgydBCB2E4zA4DIPlOPgchEU5WBCDJ0HoIIQPQriag6w5CCGEJDVIUIMGOegchMIsKIqCxDC4FoQENSiMguQwyNSDC0KImoNJNfgahGdBeBaEaUEIIYQkQUiQgwZByBiERkFYkoMGObgUhMtBqBqEKjkIH4QgNGQVAJAAAKCiKIqiKAoQGrIKAMgAABBAURTHcRzJkRzJsRwLCA1ZBQAAAQAIAACgSIqkSI7kSJIkWZIlWZIlWZLmiaosy7Isy7IsyzIQGrIKAEgAAFBRDEVxFAcIDVkFAGQAAAigOIqlWIqlaIrniI4IhIasAgCAAAAEAAAQNENTPEeURM9UVde2bdu2bdu2bdu2bdu2bVuWZRkIDVkFAEAAABDSaWapBogwAxkGQkNWAQAIAACAEYowxIDQkFUAAEAAAIAYSg6iCa0535zjoFkOmkqxOR2cSLV5kpuKuTnnnHPOyeacMc4555yinFkMmgmtOeecxKBZCpoJrTnnnCexedCaKq0555xxzulgnBHGOeecJq15kJqNtTnnnAWtaY6aS7E555xIuXlSm0u1Oeecc84555xzzjnnnOrF6RycE84555yovbmWm9DFOeecT8bp3pwQzjnnnHPOOeecc84555wgNGQVAAAEAEAQho1h3CkI0udoIEYRYhoy6UH36DAJGoOcQurR6GiklDoIJZVxUkonCA1ZBQAAAgBACCGFFFJIIYUUUkghhRRiiCGGGHLKKaeggkoqqaiijDLLLLPMMssss8w67KyzDjsMMcQQQyutxFJTbTXWWGvuOeeag7RWWmuttVJKKaWUUgpCQ1YBACAAAARCBhlkkFFIIYUUYogpp5xyCiqogNCQVQAAIACAAAAAAE/yHNERHdERHdERHdERHdHxHM8RJVESJVESLdMyNdNTRVV1ZdeWdVm3fVvYhV33fd33fd34dWFYlmVZlmVZlmVZlmVZlmVZliA0ZBUAAAIAACCEEEJIIYUUUkgpxhhzzDnoJJQQCA1ZBQAAAgAIAAAAcBRHcRzJkRxJsiRL0iTN0ixP8zRPEz1RFEXTNFXRFV1RN21RNmXTNV1TNl1VVm1Xlm1btnXbl2Xb933f933f933f933f931dB0JDVgEAEgAAOpIjKZIiKZLjOI4kSUBoyCoAQAYAQAAAiuIojuM4kiRJkiVpkmd5lqiZmumZniqqQGjIKgAAEABAAAAAAAAAiqZ4iql4iqh4juiIkmiZlqipmivKpuy6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6rguEhqwCACQAAHQkR3IkR1IkRVIkR3KA0JBVAIAMAIAAABzDMSRFcizL0jRP8zRPEz3REz3TU0VXdIHQkFUAACAAgAAAAAAAAAzJsBTL0RxNEiXVUi1VUy3VUkXVU1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU3TNE0TCA1ZCQAAAQDQWnPMrZeOQeisl8gopKDXTjnmpNfMKIKc5xAxY5jHUjFDDMaWQYSUBUJDVgQAUQAAgDHIMcQccs5J6iRFzjkqHaXGOUepo9RRSrGmWjtKpbZUa+Oco9RRyiilWkurHaVUa6qxAACAAAcAgAALodCQFQFAFAAAgQxSCimFlGLOKeeQUso55hxiijmnnGPOOSidlMo5J52TEimlnGPOKeeclM5J5pyT0kkoAAAgwAEAIMBCKDRkRQAQJwDgcBxNkzRNFCVNE0VPFF3XE0XVlTTNNDVRVFVNFE3VVFVZFk1VliVNM01NFFVTE0VVFVVTlk1VtWXPNG3ZVFXdFlXVtmVb9n1XlnXdM03ZFlXVtk1VtXVXlnVdtm3dlzTNNDVRVFVNFFXXVFXbNlXVtjVRdF1RVWVZVFVZdl1Z11VX1n1NFFXVU03ZFVVVllXZ1WVVlnVfdFXdVl3Z11VZ1n3b1oVf1n3CqKq6bsqurquyrPuyLvu67euUSdNMUxNFVdVEUVVNV7VtU3VtWxNF1xVV1ZZFU3VlVZZ9X3Vl2ddE0XVFVZVlUVVlWZVlXXdlV7dFVdVtVXZ933RdXZd1XVhmW/eF03V1XZVl31dlWfdlXcfWdd/3TNO2TdfVddNVdd/WdeWZbdv4RVXVdVWWhV+VZd/XheF5bt0XnlFVdd2UXV9XZVkXbl832r5uPK9tY9s+sq8jDEe+sCxd2za6vk2Ydd3oG0PhN4Y007Rt01V13XRdX5d13WjrulBUVV1XZdn3VVf2fVv3heH2fd8YVdf3VVkWhtWWnWH3faXuC5VVtoXf1nXnmG1dWH7j6Py+MnR1W2jrurHMvq48u3F0hj4CAAAGHAAAAkwoA4WGrAgA4gQAGIScQ0xBiBSDEEJIKYSQUsQYhMw5KRlzUkIpqYVSUosYg5A5JiVzTkoooaVQSkuhhNZCKbGFUlpsrdWaWos1hNJaKKW1UEqLqaUaW2s1RoxByJyTkjknpZTSWiiltcw5Kp2DlDoIKaWUWiwpxVg5JyWDjkoHIaWSSkwlpRhDKrGVlGIsKcXYWmy5xZhzKKXFkkpsJaVYW0w5thhzjhiDkDknJXNOSiiltVJSa5VzUjoIKWUOSiopxVhKSjFzTkoHIaUOQkolpRhTSrGFUmIrKdVYSmqxxZhzSzHWUFKLJaUYS0oxthhzbrHl1kFoLaQSYyglxhZjrq21GkMpsZWUYiwp1RZjrb3FmHMoJcaSSo0lpVhbjbnGGHNOseWaWqy5xdhrbbn1mnPQqbVaU0y5thhzjrkFWXPuvYPQWiilxVBKjK21WluMOYdSYisp1VhKirXFmHNrsfZQSowlpVhLSjW2GGuONfaaWqu1xZhrarHmmnPvMebYU2s1txhrTrHlWnPuvebWYwEAAAMOAAABJpSBQkNWAgBRAAAEIUoxBqFBiDHnpDQIMeaclIox5yCkUjHmHIRSMucglJJS5hyEUlIKpaSSUmuhlFJSaq0AAIACBwCAABs0JRYHKDRkJQCQCgBgcBzL8jxRNFXZdizJ80TRNFXVth3L8jxRNE1VtW3L80TRNFXVdXXd8jxRNFVVdV1d90RRNVXVdWVZ9z1RNFVVdV1Z9n3TVFXVdWVZtoVfNFVXdV1ZlmXfWF3VdWVZtnVbGFbVdV1Zlm1bN4Zb13Xd94VhOTq3buu67/vC8TvHAADwBAcAoAIbVkc4KRoLLDRkJQCQAQBAGIOQQUghgxBSSCGlEFJKCQAAGHAAAAgwoQwUGrISAIgCAAAIkVJKKY2UUkoppZFSSimllBJCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCAUA+E84APg/2KApsThAoSErAYBwAADAGKWYcgw6CSk1jDkGoZSUUmqtYYwxCKWk1FpLlXMQSkmptdhirJyDUFJKrcUaYwchpdZarLHWmjsIKaUWa6w52BxKaS3GWHPOvfeQUmsx1lpz772X1mKsNefcgxDCtBRjrrn24HvvKbZaa809+CCEULHVWnPwQQghhIsx99yD8D0IIVyMOecehPDBB2EAAHeDAwBEgo0zrCSdFY4GFxqyEgAICQAgEGKKMeecgxBCCJFSjDnnHIQQQiglUoox55yDDkIIJWSMOecchBBCKKWUjDHnnIMQQgmllJI55xyEEEIopZRSMueggxBCCaWUUkrnHIQQQgillFJK6aCDEEIJpZRSSikhhBBCCaWUUkopJYQQQgmllFJKKaWEEEoopZRSSimllBBCKaWUUkoppZQSQiillFJKKaWUkkIppZRSSimllFJSKKWUUkoppZRSSgmllFJKKaWUlFJJBQAAHDgAAAQYQScZVRZhowkXHoBCQ1YCAEAAABTEVlOJnUHMMWepIQgxqKlCSimGMUPKIKYpUwohhSFziiECocVWS8UAAAAQBAAICAkAMEBQMAMADA4QPgdBJ0BwtAEACEJkhkg0LASHB5UAETEVACQmKOQCQIXFRdrFBXQZ4IIu7joQQhCCEMTiAApIwMEJNzzxhifc4ASdolIHAQAAAABgAAAPAADHBRAR0RxGhsYGR4fHB0hIAAAAAAC4AMAHAMAhAkRENIeRobHB0eHxARISAAAAAAAAAAAABAQEAAAAAAACAAAABARPZ2dTAABAaQAAAAAAAIq9YzkCAAAA7Mjily8oKZWZqqazGx4hICUnKZKQp6urHB4iHyYoKZeZqKenHR8gHicpKoVNVlpxgVZWVtzQT6lK3aY6hCTxrHzcbybLBae+H/7gvJlaHnnA98/6j1Sv9n+OfwEU7bNXZ8BVfy6ac/Y+A5h8v31eg9VEbTIrauvfV89Dc8XTdy48ZZzsADpX3YyPzaRvfYqQXhc5t1nf7iBujNuTY9zcntyZTRecoJOd59kMkMIqCAQDwMO31ug7xu12iZE0ab6U7O8YtLduHN5squlD43r7dO8dherqF1XVV79dXdUZCRV75CL0ZadLmkDM/lj99myzAACAswAAQK21Hn7/h29xNxgAwLx83r19+u581ztg6N05JCiATHQkFpAmPrY8b/0HENCgNjyAiNzU6JjADERbckaCCAYo5cxc5gj+ZM7Xz7bsg+Cj65+6/mHb6lpc9osD9+FmYDDoIzeBo3rXUD1P87JlgSitkjivR+7E9EltTxlZXbI/903RLpJuaZFsNUwMZFVL431VVKC0yww+xIulXzPHIllGY7nFEst/vdlc7ZBv4eVfEZLaqivtxvRqvXNsmWACHrbkLq9v+snukudH0ibZVytuHM0gtusjM8PGRSiKYKm39SX2i1S0V5JTnh88Rn7kArcaH8cd2iTcGs75b3Rb6Yc2KWByklvLfnFOh650fSBLH3hmrLx4asqRLHjfLSqiAORW39tb2UAeoeseaPV8Hl25DwZ9XyYccnWDsK1kbOg+Q5jFYT3l/kor1Dbi3FrtCgUBdZEWtuWopjlLWG+6yMmd2bueTZ8PeAD+tbT3t94Qo7kfxfbVihuxfYjhCK0xVh1KEoNlx86+tneP+VU/1Wrs30/R90WrHtrBHKA7rZ7cXnmp+7zlaHw5nu3CNzATo6pX3Wm/0+rfmkzytlQviVfo5Io/f4hw8ODka2US4vaeWznYxBpJvh+zEy15KmnSdyqjO+9beAn1j/Q3E8QxylcqbIKnu8Jn9IudyNCOJqhp042QpRVV1tltx9I7YwFoFrbkbq+v+chcgp+PYJt7NePZo7e8Mo6e2KxXTQCQGWvPY0EES8Myk9zGaoJN07VN+zdt7wRODWVfvmfWNzGa+edsjmiT8pMKiB3K2/v+XogryJp1OJKwYwQG5Ix3t7XaD81ut2vIALCN337IMggBUeFo+88YxizPsVXLOqnJHPbX3nfCm66YfuaAZop3ll1YvUPrcdWTyCU5N7Wu7wgj4nIriqer1L9uhArfCnfpDv4aBQCE0JhefPA9mQ0gYO/pQiZ0YlPym/Wr9F2vuwJ80K5/ZfrhNOPgQMNolnDXKNps+hiHBARh+xlhlQaEzpWFUrBYV3sONoUHANCY1ZtpjKeACjeKHlZsIaZUnAGE0FZ/J1w49RhI4ZwA/MKXoxpfrCo4i+b8fCM3pZ4SFoTQU6tK/77J0HXqTECSY1DtkYpSwY1+xSb1+txwDbF1EeoJ4gE87VNPD2betGWtawrafs8bhg1WTg3aP279KCfHWqmvBzk5fK6ONw/87lPlM+d/d3OPaqcAuGKAZEsAAF/e7DVREBrV/7sEYyDx2FjvQRfATxpH3Yzbj379MzvkdH9J/QSu+bRHHjeMMYzbn5pMY6SCpAApBhHBAFoLJt6cLJ8vx/j87f/eWtKQr08aWF1dXV39p/90Ffqq5ptVFJaWTfZHS2bZbCZLy9XVL6UaAABwVisvb6mvm+au1XsHAMhKPbx+szE97ggAf0cHL1lVtR2AhXOoAhOIMwqISUzT10zF9sICPracH9PPHmTgNS27pjhgt2C0JSEArMTef9m4bnQFf8BVH27MasqhFp0MhC9AcDre7viCTMj9ZackGTiBgJqIs3R+bMdagTYa0w1a5Hk6f3PXm5Gp9nE3Cg4isJtvFUIfGuwYEFewVr65jVN8v/oIdsDFRGrMdYjNoVvHYYuviGK3N8J82fsC7LfeTBWTytRNHrbkj6e+lsiWvH9r2nT214obWnZb9WbHDBsjlCCQhvZ/lawttbok52g92OqHn8GYbaudWsp6tJYF0KiHkf7PSL/8TiT+1Ch+TQTIbDfgujX3ne4yrpWF2KY1UDhGSwCALPNDPZIByGzLiQaYZkHcX3j9zAYhwrFjgsqjkKFeD3sNhilpVt48+VmtB5ZUQqj1ut/VN4/eN8PfNUMaRLkuRRmhuNUtswEetlTOp94GMornozg82asVj9hOcxuzYEPPADSurkZbksCyk85bcp7Eo/djT+160CLW3GC9Q2VEWq1T1StS+tqrxZdTH3z7vPNaRj9d4oClUT7FIO3Z7mSizaQbEBkGim6QoCSX9mXzvBlifk87tsqLCNJSyc17bV3E7nyDVcbmGrVa4q6S2HxlQwOv3efFNWhbZ6/bdgftqYm29oY/7CrZ/te/CCBKdfBkKAAWtuSez5/9mCv6vpIl+2tKzmZ9ZD32ON/TerOWEoDMWEtQgoAu86bJYr+xSrL4ev/W5rWTKpfucpPOT5mCoFGdR7L8ciQLkk7KBxzZNLgEp+E1o1WWMsJr/ySTSoUgXhRqDwAo/YfNI7mQovGfXnPZZas7+pVmk3SC9qdsLFQMPgnbV0z3u1upkBaybTvUjL5NpVCZOHHJCqjY6qNGIBlR7clxGzEnyK1BFBmE0BRdmF6XGQAcbg99Z3cj9POSY+MmtaYrY7oEjM4uf6mkhndus0nDMXotc4ju6+0fBwyZCBxbDTklhNCzjM5oDyA8AMASsC/gJU1eSQ3OvSTJmSluh/Pep5DuDITQFn/h9sKzImj6AUCT9tFkx9UioCjHe0lAjBKEGhOM0FOMSl7XN+x7YQnQLXfxM3N1vRGaLvO7B8eHyYk+e9vZ2w7sozTvs5WeGN8P3aztsKcEoPZkzt/t/MTLVeRe++Hzs13m2YyVdphmaAD87rPiY4X44xgA0DEAugAAIPEZgAj6fQ0mw382Q0eSGUcTX0+Pz1WAH/o23cyvX/r1f+5I6fNKTkPNbHcUz0aPvH8V1+3BGANgBqngBD30GACQchARDKCt6SlGwvvrjFu49n2vCe8YHmOMkbPllJfxCLGahEVTecnq6urqJzAsqwlLy1o+HzwfGXj/WF39J1tkAQBwesIDAJD1+M2TPt85Hw4A4Owc/p9stLrKQqW6eqsBQB/Kfg1Z7TUIP4/IRQE+tjwe/UsgKCbUjCPMGIOUJQQDnOvTPs77Ts9/wsh9u13vSj60SuR877FLzPpM1Ufa2NeEX/XdGi4hr4JrLyGdQJWLTMohssDKq65r3rG7nlnFo6wsJCB/u0kNruBNkR6chni8nNpWWYwq7tDNuUHmNks3VKxeQi7NLm0qosbEfOQn/8d/Kb6y6I5QhlT/2dRV5pzFRRazAWgetiSPx0/+BJ6fzTrr14yHoDnE0XF0HB0JIEtsFJ1FGUjDtfnUkdhlSpJ0r5icNVwoDaXBMU02jpHJu1vLct1iWQ7FXU7ieQVK2MRMZpyrxq4wTQ6UPs/cmsrd2/g7mT20GgRZbg+2scsAuMi5t/PV6+ezedzbFblZQ+xKDnW0BYY1i1zfHO6IztCA9SiMqXQds4Lu0TA+PiWF5soGilgJXDVX0rgHnQcetrSOt76SBMP9KPb/GpMjVtrQaUEkAD1jHUoSgkXnnwHv+S54suiuSlW1VbCxCwdqR5XpPD9KdzpcSPfqa+qAtS/urLXt3SKyvg/05APmOuZO4NQ49s4mnWxqfXvdojthhECj7Z97/8R5pk3R3PI3ZSN22CuzGO79pplWqmzVAeK6qp7kkMzrV7gf/pysIvPXy90JjbRUbAcrqSuxB0dvJbX0TPQFABa25O+vr/jZWPl5BdusrSnZbMbRkat4GXFkjI0oSgjYZY4JLv8S49r1590Pj5+fb7ScZVzGv9DjemxDf+CtZU85T6WAkOJQPsD1L1QmcfIzavzPX59P2jLn6G8/OSrb19DKBQDl+lGNnSow5FYcyng5D6T9DEmR9yaHT20Bg9Gql9wSLxPfsd/rgws4clJJkOLq3ojp170CUGEQ/1AP/kfxdxtY1KEDfNDcU3R6kUoLOOC+rbaG91asiQx0E2UpP/Cg9wCE0LMKNZgvvPMIajQ480Zsibb4jx8PCeDYP/5HyoMMhM6zVZV5ogAasAToJHW3oUCJK9PEZouAnr71vtlkLAWE0Ba/u/DNiw7hA4DL+cd4OmJEHQD3vdmu5lYcYgHs2mtkNqOncXRQM7b05n/+q3Yy215YT/4dL3wAAfw/bpPst3pikgA07YuW2O/vYtAZEcRNRQIbBxx5nCYNB/HaJFlLCMNz4+w0caYCIcdbCfTu36C/5+6rIZ0XQOkDRDAgfxXMcl4DMFuG6wFARVBlZH79rQQgeEbBCLqJZu/lcwQj6Z7//WP1SxzqgnAUUAbeAQDAFBgjGWOMBIJ0BwAAEgEgBfKOMcYIAADAXLJpU6DWfLYmAECMMUagn+9O37399sazMTq8fvOkrz+eDvLQOK618u0W23zNVldVawXy8KPee++9fyRQAgAAf1peNgEAnMF6dfXbVQT9RAUAAAB+WT5v6beuQQS/tf/+68tbT4rgAYBJAEDyBQAAAIB0BwAAAgApAACcQwFgbQAAAKBNAAAAAIOpZhSN43x+iKuk8ywECqqAhQliCgAAAP4oXm8rT7/2IvgIP/6zb5rsUUa/ACC/AwAAUAKkOwAAEABIgRsBAAAgFgAAAADbzi/huklNK/jLOfYTVhp+DUSfR2GVBQA+pwAAgFzeYxSMg+YMAAAAfvid7gdPv/rAr/T1r8MeYfQrANMBAMkXAAAAA0hfAQAgAFJAAUBWAwC7AQAAwJkCAAAAbOJ7moph4KPrPrivG+29HU313NLMp7juFsCinAyDDnBO8CYAAAAA3rftt4XdXn7gM/35j26yRxlaFg2gAugXAEByAQCAKgjSDQAgIBFwa4y0qw21eXpwFYC9AgBrBAAAgNkAAAAADNBsbh8jn8/96f2I2ZimSDMGPJDw8qBfPWYHijM6gEVjIktxsac7vJjogAbwRwAAAACel42PK7fdfJiW+faf6MgeZWzH3BsDElQAJgEAyN8AAFwFMbkAABRBCvzp2zouFdaS5hYA0N8CgDcAAAC4CgAAAExpah1fOgfHhOvfHxfVrI9rWyIGpCwXLO9lh6DXkXutr8O7rAYwG6Bfli0AL4F0uuFZDmQZ+kgPCh3gVwAAAAC+h02PF++zeXCs4e1/PzU8e3dC8ApADQBAPgcAAAiC9AUAAAEGUgAAuAAARAAAALgpAAAAQB8aByDrt2c3U18Wkj6RDBwWV+44SwKwrziz0IC/AAAAAL53Tc8n37N9cCzh7X99m+0djCYAMAMAkhcAAAgEQfpKAAAEghQAANwBgNwAAADgFgAAAAC06V9FFLB9vgxNz6/7WRkPYIFCo3Q6ALAND8AGgF8AAAAAfmdNjyf3s33wtKS3/6HN9i4CcNAAJQAguQUAAIEA6U4AAMFACgAA8VQASCMAAADMrgAAAIDUYzCA6Q+d7h1flBELAG44AY0+aQCgMbvTJIDlOwAAAABPZ2dTAABA9QAAAAAAAIq9YzkDAAAAvce3bCNcWVxgZGJlaWx1bnB1d3t8fHZ/dn+Bf4J/f4GHhIyGj4WRkV5Xjc8H9z28eFvL2/9dTc7eRQgOGrADAIB8CgAAYQGkOwEARJACAACrAsAnAAAAgFUAAACgn5xVgYn3no46gXq1W0QHgIUwwvZ4zGIGoLm3XQelN809gBMAAAAAPldNjwf3vn3xtoav/3569i5C8ABAAQBIJgAAIMDASHcJABAQpAAA0FwAYEYAAAAoowAAAICYAvFKY04Fario/UYJALjhWfy3KBwB0AAqN4aIRcECzQsAAABeR43PF9+zefDVp7f/S3v27gTgAUDPAIDkFAAAAsFIKwEAiCAFAIA7BQB6AwAAgCkKAAAAyAUAM01Y3B8aTOlz9gA4gwqdu7ZSmCSmBzwA10rJYsNOmAAxDAAAAD43Lc8H3zu+mNb29r++NZm9i5AYAPQBAEgmAAAgyQBpJQCACFIAADi3AkApAAAAIAAAAADULaOyAeBuJp4L57q4UgCgA0DYvVxJ7ZS9McQimbNAMzRqcy09gEo4CQAAAB4nLc873zM+uK3l6/9/a3L2LgIDGmAAAJISAACCwUgrAQCIIAUAgPNXALgCAAAAVwAAAACgk/Niybqn8W6Km1l9Yq4GAEzkpU62mAoUzj5BB52U2oomd1Ij63rdHpEAbxsAAAAeJy3PB9+zfXEs5el//bX2LkIvAFADAJIKAAAIBiO9CQBABCkAANwLABwKAAAAnhUAAAAAsnsJGY/69ym/83S/w6sDAOjsDCPbBDnJWkyaniYAA4DI4En1uOGTQJj8CgAAAP4mTc8n7715cSzp6X+lrb2DETwAMAMAkkoAAAiBkd4EAJCJAAAwCwAwAgAAAEQAAABgpRx5UHF2RpKPdVZs9LZhAcAEF/NFy5os7VeYl/fQKMCkFDD/7txuCL9RFp5Ex98BAAAA/hYtzzvfs3lxLO3tf3MNZ+8gQgQNUAkASAoAAAgLbLIJAEAmAgBAFAWApgAAAFABAAAAWPpg9zhXsuqjw/AqzI8QSxkAdDhQrm3HWaYGTtQjQAHoQNc7zPWaapXs04gHdAoAwS8AAAAAvgb1zwfPPbyYlvb0j8Vl8fYOJuAkgL0AAEkJAABJRtKbAABkIgAA8AEAUwAAAOAmCgAAAEwHZYa8KT7fi9hwO05eADA9PfukfWS9VgMze6H3HWkoeHig0WmMkHf1JmBCjND9/jUPCnwHAAAAvvbUrzffk714yvfbf12OZ+8AQgwAFACA5AQAQBBOHgAAZCIAAIQuAOAKAAAAMwIAAAD8SYzQkTXzMLfJCi9SBwBA91o4WJOnkPiUWi7bbv4WI0Nn2wxrHAomYNLolvkb5xnqYs/C791JDssfktBpy4kKAAAAvvbUrzffm1585fvtP06Os3cQkgOAXgBAMgkAgA4E6QUAQCYCAIB3AAABAACA6wAAAABgy/eluiDez1pBpM0ezuMpAAAmdLhbXomJlh5OpTXBkhumoR4RCV6fii7WOKTMkJmbDw2KGQIEbAoAAAB+5pSvN8+bXnzlc/uPlmPtUUJyAKAEACQnAAACg5FeAAABiQAAEFUBQBUAAABuAgAAAABbi4/Oztzi6+gxiIes6+lOCgCgM/FPkWC2UF+yYyWvORhFgatxyXaM8H2mmjppciWBZlRDL0Xg0YkYAEAAXtaU7xfPbX9M+dz+uznNHgX0A4AdAADkAQAwDklvAACQiQAAgAAAWQAAAPAbAQAAALCxeBWTaphnZrgOX7t99s9EAUAtdXtqCWZvcHy5OtGe10BOevtqK7bS/TCQQGp8b3e5NkqxO+c6eooDaRyAThMAgAAAXsbUrxfvnX7c6tn+05Nl7VEggwQgAADJKwCAIhjpDQAAIgEAAFoiAJA7AAAAlAsAAAAASGbOE9sIkuxl3Tavxn2kncuHPKcJgA7tE8eVuhmzUTLDMCl+A4qTt9S+YuZ2MImO30VgigX1UmDe/IlCN00Fv/9VLAA+xmSvN89jf9zic/tv5Wh7BMggAfQ9AAD5BgBABUZ6AwCATAQAgDoAgO4AAACUJwIAAAAg0mhRyp+eg4g8Y2gK5XxdvTHWcwLICLklaSS1++fpgyeK2n8SUa2Jip02C24HWEzm+CH4+hypmBI5qjSXA8fQG5qOFwCAAQAexmTPN89nP9ziPf0H5aj2KGMJAFQCAJDvAQDgwskGAAAhEQAA1BUAQgEAACjTCAAAABCi+Fr6My8OGofytniybSU1yH/e0gag0aY2yjThXGXhOv0SGUlxqGALqZ8VZx3LA/hOJ17H6Xu1r21f3PfkkgqewBDgEx8AAAAAPrbUrxffbX885Xv6TzQntUeADB8A+QYAYAQ2UQAAkImAOAAAAlBWAAAAAEDyeW4ldt+miRyfD6U+2N5cW1WOIwAAANfvAAC7n6ZUjOLOD4w9YJMQO0YtuuA7K/5b67nOiQBa16hcR0W24Zvq17NTckGqaag92MkjAABgAN61lM/HfWYQt3wP/+lrDmQPgQwiAPI9AAAKJwoAADIB6AUAAACoAAAAAIDwrP3BFN441LTjDzJDt/ds6ne50gAAyIcAAC3CmqvbkL6/pDWYp7da+92Zw06mVx9IAMidjDYqqdyGW94yXjZEX7/C1mEf828BAAD+tTTPB/dJP5r83P77yZHsIeAgATgAAMkFAJDDiQAAxAgAAHMqAGQHAABQphEAAACA6VBy8txaT5fDdctp+pyc1IQ04soIAL5VIbwRykbCu2dUFUyqnvzO3Wnu+oSdRqRuZ4EHTKYUS8Xv+hDgl4SfpsUec9uc0UbXM/7gJGEC/rXUz8d7WYgj38N/Opd2IfFJABIAgPwAAMQ86QAAIBMAAOAeCgBfAQCAA1cEAAAAsCHyDVXG3CbKk61D6VEFEzgtQEUSfvcmElwGZ02MPWPDftoexZBF+mPDkPHAZKDM+d+JvPmc5M41DyJqCTFizQITvAAAAB62jM8b78l+bPU9/IcnB7IHhMMHQL4AAEY86QAAIBPApgMAAA4FAAAAAAx0nkElyjnmYYXt9/jt66q0U8GPaMYEAAB8XgEAI9lKdYfBJHtv2ySsoXl32nL8uzfOxN2Nba1Z8cCEBetbJEkWYbAzTTRB5NKhxYAej5XMmg0ACAD+tfTPG/dKP7b8nP7TebQLcAQACgBA8gAATMQqZAQDAKQEAHoBAIiDA5Ci66BIrgfN8946fzzPZ3tbK7Qm2827AwBYovqxnwuPI0D6ZGZIuG19u88nsHpE9PkgQgf9I3hUtvAzAgAAAKCgFM9YFJFnGfFaljZQkrSBuAEd/QzQMQEetnSvJ++xH7Z4mv8G9kBxkAD6AQAkPwMAqHjSAACGCAYAeAYA/ggA4AWAARwAAAAGcH64NQ3rTNB+e1bTnd5bDlacig6A+PPbY8USLhbiXpzWHtY1v3LfmkbchV7xVo4VfEnCmqzv1gOR1ewobIUlDTOQAAyD7FwV1+StAWACHrZMz8tnGURQn9v/PB7rkUgJACQAIHkBABjKkwYAQDEAAGhWADAFAEDhDg0HAFAAAAYz/ftfLGhvalzdvMS2g9ZuxPJ0FAA77gPL8C9FYjLJs+rkPUmLuh9ry2xGlYPH2KVmBE0XJOxkhW7ftmBosQh8GdB5IHcmQi8rnKx1OFNgAf61NM/7+0iILd/bfxrVSEYAoAYAJB8AABKcNADAIIIBALcAAImA4gAUggIAgAIAZC2J+nGW73P8FQbR7we2E78Z0soAAHg3V1qvb1ZOFVBOBlFCaBC2typrGeMvMyvSZDe1raChR9E5GUfaZkBGr3cmlj+F9/Q9gsZfdU9BARbetTSPO3uXP251T/9p1BPj8AGSbwAAOZw0AEBi4GiAOyAIrlgHAABQAFCx1qwgiK0tbbj5CVZtP/feZ6FW1gAAAPv/AQATsLeQo4ciWu5SdtBfOi/3LBvR431XhzjZIQtIPBOj0OeTGfuJ/TlqsiqVK7RaLktQ4nzpT9uZpRcA/rU0zyf3ej40D8V/nxWuemQsJAAJAEFSAwAi5SpkBAMAvBQAogIA0ABAcnzLs0L8lK27T42Tc/9+25h6zcs7AGj6vEgJRcBlLhYwi5BXVjckU5UGVvikpXgg1sza0Hqtlee18jNqAIABALohz00flzfNAAVeG3dXHPAdN989BKAB3rU0jwf3ej9ML8N/2RJXPRMWHwTJAQBIcVVMAAoAABzxBpC0PMALD6+dbioZxtwbQd/Su+3QOQYAAPj8HgBAdAPbTZiiTnZwNzuRdJCL7UZvc8aJDSu8jQrij5rc624QofBHLDSHqAEGAABwUOFmB+oQiZjO7+1pfJdaLpw2DfefGlPyzgIA/rWMjxv3eX4Mn6b/FEsc9cQ4AgABAEFSAwBM5CpkBACAmwgACACAgAAAIMrMYZO6NyKm7zTN9nRrrnYNZm/EA+Bp6jwQ7qhg82HVRk+z4iPyvL2yj+OHkqoTAtfskzTLDUMjjLPHTAAAAwDl3M38/vuxmtK3yYkcI91jGq42C6B8mh4CHrZMzxvv/XxoPuLwn2pM69G4AWgYAYAAgCA5AABGTAooBANAANpkhwQAmBcAyAIAqgCi31v5m97DQ6jeb3s/L9fCBIDcLDu0Jwnro7EdO5Xa0IYwqYq8sSvm7HNxelaQ+xLm98jcQ9aI3mVMEAAJwKbiJEqTOZUvnY0z15ZFsh+KWeDn2pmmRkcDIAH+tYyPO/faP2wPw78V1cKknhSLD5IkNQDAjrAKhcCDAgAAHADEYeIeM3posxTdRVtR311AtuEELAAA+wMAAOgcBrXE7QiMG1ilfF+JnpIaLqIdtRZlh3xAPGKT7UFMFZMevN3/kFEAAIChuKXVOPLjeie/cxMi3zQiJVZn0xKSfY72jpgLEv61jM87d90vjkeY/isWJvVYHAGABIAEkgQAhBGrUAwGANRWAPgBABShcGCQgHp+sL/oJTwf3pyz/rmfJYV2fC84BYC9mLMPKq79CCygCLYeqgQXPdYtzTuQTWrBYbt9O3oEZjeL1mc6Ee4JRI0FCQCI1yBOS2+kbFskSQ6f1T9yoXmESWT40octQy5NxAMCHrYsz/Mzb4jp0Yb/roVIreiTQZIkAACGsSrIQKIAEB0XXGGdJ7dv6gxyenPzfrj6NQHUN6qFBQAA5hQAAOCa4N9ZOaXuVr0ujP5IefwaJxApIZKbFzd5hygG9BgnQWlqXFwxB3ACAKzKLsapnB+MDL9sLL99L5VDslSCSoaG1b4tz1zwAP61TI8rd39/DK/Y/FtpWYLWjIMEYAaAJEkCAFKulhAMAJgWAEgFKAAoHABmaPW988+rq3xmN5/nZq70on3f60pBzx5q231pRcGr+EoTqkSgBiHDbZSprIoaka6WvfDjdrbi5bHv4AssupkdcsUkSQAAsGoz+gi21HsSJX4dWZebOlJW1Ehgd2HbVHKj7jXMBTwetkzPO895XzRXbr7+tzCpFX3QACUAJJAEAEBSVYwAAKBWAcCAUwAAcwK0e1rL1Q73A7r0S/B+H7Sn5+IzkoEMGv3HsmzzGiVmu5K0ONmWhZPVp3c/auT2bsXVvWX0fyeyrb6fYvN6RBaJpRTn1ewaAXiWTRZ3OUADd1u7fouUuNZsqq8gWsjrI65Ec2C7C7IAT2dnUwAAQGUBAAAAAACKvWM5BAAAAE+pIT4ci5aPj5COj5SPkJSWk5aXj5iVlJecmJ2cmZuanh62rM8L77p/BA/N/4WFsFYsZJAEnQCAaKuGyEAEwIESUQAAh/OmT5stzOSqh/zbkDhf8RncsQALAMDnFwCADePhqf5r8xcrM28Vq0n85H23Ny22r3/t2rz1WneDc8sd0H8ff1Uo5oDF2XyFJJVJilI4EA/s7Bn50oS+sqYNCulFeSKc0/XOFhilpy3+tez3g2c+P5JXHf7HFia14ggAFACQADoAAGS5WoxgAIB0AYCnOACARBwSyA7i1xWt8Sv64EZQ3tf98Yy3H88KLisgtNIm23Pfb+DFBhJyxLNzweDoOAXjrdOR0n7xCbIDjFvkOSIVciBr6konlB9DB2eCp46ULTsjirrlLH/puZOtCW//zB7oj0zp8i4dWnIwZiQPpQDetaz3Lft6fnAZvv1r4atmHAFALwAkiUmNJUYwAMABAIBknL1T8cst2+c6zWV/zNa4vQdXkTTsOg8AGZoBMnvPspwqAADoS0MTfXgbiPdO7Dw+rlV8tWwWb9fTbeVg6fHvPJRDiGOhlzHvs3g2YLTsYofcizxyoxFxBGyVqSBGqNk7kX/oWn0/CMyeO4EGDx62nB4Hb+0/klds/rcsoDVjCQCUAJBAjE61GMEAgDgUAABDJuLXokbfH+ha5cvvVx5fZJpywFAbgBXIXx/Ox7dK8BAAGKBkDSm2qMiNeNctxucg0rMJbeI5L0q+t2VWnsiINb6shIt2v/vah+6VDCM0iD3zGQDZ4Qi11rVhA7+yBt9m5/3DcMZsJ57WYQF0HracHjtPv3+wav53jY+acfjg5BhlSQwGAEAZr0OeIti+kCek9waVPbs6foUOAMD1JgAAKKAeGpH3gkfWZ8eqV07FAICHbv/VPpKZTbTs/9GS3FYzrwmLns1WyUe+WxxmLDuZhwm7A9jNR+VZpvERqxIpRc/Cj09CEjObEs0nTdj5pFGVhWV7K4gEngbfTNAAHrac7zvvvH8Erzj8t4xJzVgCAAUAJCKmYxZEAABohwAADIEH5sr/Y+R1+OvcYrbLXj3BIv+CNyULQxsDe+GDQIyDxmzeNgAphiHMY1fZYiA5Vo73otfEwjImIldmS3Xb0uf2U3Ziwop5tySU448PqjqPncXIDmHxAn8tlALSiN2lZ9Z5eVBi62VOSpt4Ev617PeDuz0/oiUP3/83JrXiCAB6ASARWQwdi8EAgDsAAAAoaw1stspr3+IZ1Ffv4YeAmatwJW3XIwAAWVVgy+mP+zQ3BK8ZGCCHSbgFYU57eE0qB5EOrcX4ZX73qZTW3Cjd9m9i8Pj8n591VsCtvOYHu8l1wB9xggxKV6FFaNt29eRG9rio57XlZ8MyUdABHrYcHyfvPD+CPjRf/bQAa8YRACgB0DeRLYnBAADNCgBAYit9H8RyjoQlZ+9vxxt5mzoTNnJ5HMwALA9ZnH2My3KwgT8aADRQTVzpptfPzmdz1LxbOYjGXHLGTAvRW+/9M81RSlXD96e2zVN2PuuMV0dNuhEtimmQQVDf3O6CzaW0ABdrtLYu9yy+vg9gMlnb9j0ABf61XG4Ld18fgiUM/1VjqA2LD8lpc1WQwQBg8I29PLb8vu+x63HLOu6zqtYvx4+W9wAAXL8DAIABsnuPv8bbl8x/FgC/kTmQs0jSjhjIspptr7E4a4cbSYbAbu7ZyQIvE+IoieUErFsGqTiaUCyAYXINw+NehmaUdWkjwwhso9+ycUEiDl60cUsbXLthKYAA3rVcrjP7nD+qJU3/OwG5VswAQADA/ejYkhAAAMITAAAIbYfNxgv46y/Ubj8d86djsSO4djbRCZjP1IR2VNyw0ZqMOQXvgxIyZaGVKM8xFS+z3Tp/rofDbI7QQfvRoKzUrGYrJBuf6ugr5gDn8cSCuFygX50LqYsZXqTfrmi+bFW1pre9zxVLPYKknZPnBIIH/rVcbwt3az+C3vCfSKgZi08I3AAAqHNVCYEvaLVAcFQlBgeImUc3eq13mVd5ezwfs6lL9aiP77TRbRQAYH8YAACHbQjo2s9pZlytGHf96mrOt940WaXi7a00G3+qt9ry9sLZill4ikZ0NmPcuxceIjBjQ0p0YteItTgM4eF1qO0mHf83RXYi562iHgGc8yxfNMIHDR62PG4L74wfVW/4b0trxhEASAAB9AIAFDqxGAwAqKwAMIACkQpeQgcAZ07Xr73dm/jhvL8fvxaSKo1yWu/yFkviLhBLqyjP/m2qrFWyJhVL+fbV/WuoM1tZeT/ejlCge287Md2VmL4M9aEaEg8MAe31ybfoXlq7dCe2DXkxIvvT9lH0JlMlwS6CtktYgQi9gwW38R4CAB623N4m3tIPq0Xzv1quHRafALoBAEaKEgM4OBoBgAKwF/spsf30Yw+3+99on767rAUEHTa5SrZHAODzCACgGS71tvu62Uzfsd2bkP3vStbPruxACWaJF49nYfhRbsLaJd7r71YrlxhV2HvbCMb2MALdOWaYwqiSdH53dYEKT0juw3KnF5wRZIzPyC4wtlq+ETovAd61PC4Te8QPOs2Xv9SK5ApAAYCVBQBQ6ixLDAYAiBEAQHu5RATkAkBgRzvMoqOv3IeNhqs8N1eCIx7aAUrMOzH09+ajZBjZHgLEbyqd2m4Pue5yBcyzaCu3vWbKwN17i9i5BWyNG+jF7XLGm/3eiKzsQzMjBrDlbE5KbrYYS9jXh5/1bDvqK5BhYuo3TXMhN7cUVyQPAN61PC4je9SHIBv+A6gNSwXQCwAnR3A5ZgYDAKoDAABivDTvccHuDrkyer3ruopNw6AT3fEsoJnNaWBW9V0F9qXr5y9ckZ/KRTIsxCLzTUSqirgr5UauBIHNkfZdi7mpd2IS9PuJaR+m+PsE/Yz+c30+au7ZCfucHW/LZ5HTX6m52FcTovRA7SHTeGyyZx8lW98IhaDRAQ8etrzcJt7yD51O89+Qa8MSACgBKl7IDCECAEBfFAAAUPD0yX4Vbcw5NZHHqJK1tviJzxV1ZQL2Nkak82/lNy1CvSe2Smq5bL4GJxlqmTU4YcD69olHYo17g8wkMQeySvfJqFTkEDOBwifrprHyOm3PPhWtWVe/wjxPCa5orh+BDph7OZk54RvBb/pwEP8wWR62vF4nHvcPUVX8J1Er+kWVBgCkxIIMZjRgUcW9cFci7cr1t7PuiJx1cvbweD7S5CdVGYoASwBgTgkAsDPVIRrY0sU7a1HqKbJX3O8Znfxn8a2sUyy72m45QWdaA/+2Xa/ynY7uioEFZPVaxDMqcBxRQ8Zp/eXcyBYlmaZOxGKdwSVm3fvA1Zpj2iDDfhKltKIdUVgB6IACHra8XjtP+IeoGv4jUStuACyrqMgTQ4cAgBtiUTaccxyep4zxKk9JXav2jE/TeqT8MhbEAMD+NgCgiMDanNp7kX/05q1qd9riybz+vymAlcrJxZ6ldYLmX52jf9e+0bkf2DAq7rva2MnYK3pEsrBDOWXPu4axgd5ZCnwYvnAxldlkbaU3K6DUFACR8xMGFf2e93JxDwDetbxcOnuIH1HUPP/KNeOoAJQAY0cqoRgAAOJQAACABHzUdFf7/8q09XA/Hx8OPgell4o6mamsooXKparZ0yXF3SYCoBjDWBuTG8Vm0NETtFU9RrFBlGSoK9PYTCj6i0g4TqGmb77fbgXD77GoUXxm9iU8xSMX4xEyFdnVNyhlmm3smhRKEemE0NL/4NmyLmRuHv8JHrZ8XDuvyw+9qPngz9SMo9BMOSqIYAAoZ7Z8a99zic+Jpm14PNu112EJKRYqM5OELABwvQkALFherfB63vID811ejhVMZFeRjEfgil7ju926pkEW57A0UX6w93ZQ2cnJx/3lgWEwhNfdI2/7NhU0J8iyJbcQz6Qai+S4bScZJ8tdmrtp3pqLoQ0i3N4U318/2bsiKIIJAN61vF46O/ojiYYP/lIzjgpAAFyJHpXEAArAdQEAgEQ7axzftOur8C9TGvtBlzJwEupWj+FzNL4DbnWP2c0V5tLkEE5eSFsqT9iUsLI8xVlVbRnccm5Log+ccJjD0+l3KC94F3H5mrMgdWlIpPcOwuPge4Ien0oiobASZg3XR5U/6WG4EbF5V8JDm/SiJdMc8poKkf+wDsLVEO2MBB62fF4qj9Afnah5/qVWLAGAAGSaKAkiAAD4FQAAqR4Lu9/fnSNfd/e5f76bIfvkJyq1+r1j6sp16Hq46IeDAW+6Vd/QRhsyNVh0tTGnsJ9UDIUlc+V6zjIRNny5ANfrRRNvO1xV7uPSxsF8VP+bAusjss220O9T1/a/h0xTfy2FeBDSV/PwUBIIM7enylQTIdClsySKGZ0F/rV8bgu364deNn34T9SKGwDIKGFJJzFkAHCvopzPi7y+8Ch/da4tLc+O2bwdCk14FgD2BwCI7r0tJhzGqrxkzqGJdsxaepV+O9F/Mnb2dajKmYsQ5ALNOh3IE2XovoAuQB+ZKH3ifNimCGbbzZC7VULbDiGHLke1EqTB/yjvnpzzKWr9C3pZV6qq0TZX28l7w18pvG+ChTyyeAAiAP61fFwKd4gPK6ZP/9SKowKQgJ4uhJIQTACgJgAIAEBJgnqYGCVyaL3etgfP46EFhFAiC+HL6twwZlgwzwH3cNIQXHdsxAf5+3puxEcTJu7FIkDJvrfzk54vbeSlvklRJIZcMYswd8M6RXEEjDiMCoy4PQv0UZHCylFOkC22e3+xbT8+NsW0TxNfcmjsXr7gdplAHHNij7jsTWvgAR62fF0qj8sfq2j4+pOasVQAApBeOsNiBgCAmgAAQLhla0bIDHG+Dk53rz/pc3w4ajPIixt6VsMdt+PosB7n+lBV3ooZOSo0BgorHpo4KysaXkkXIzgliveyc2xcbO/jPfFYh2smSmVoYXebszDXUDm2lBboEzzhcYpJBTRb75zF/pYejNFN+qE+BaY8ve4hx32J2n0e7hUICR62vN86L/qDZHh+TK04SpZoJCGCAaQSSedn/fq7pf/dw2g97M4rDFDZdcsx6w2SBGD/F2CYFOkOoA+1ePK4o0h/Y5wF1sZ3gWpPjMoU7dkY0DX6cSgOQuKmODY65+fPZm1vkO2oRnFFmq8MnjOiTjtnMc98/dtNJtQcdgQvqvnM+4FIlIwIpU1Xz9SY5cXcxOzv0pa0XEWCaHgAHrb83EZetx962fChL7ViqQCUAC+jcZaEQAGAJgqgEaBHRwSmOeUXRLkx2njVX7XVFegleukVDES/wBR3O9tYm+m2rTYegvLhXjBR6kjIhlsDSJWIJRMYMlWpFYGMrswJvq2x1/EwBbRK5iG77VsLnnOOek2RzRpcsVkpSurz/Wfr7EWpF6/VvTnDuKP2MJB4bJNCrDRuHO70kB62fF8qr8oXHaaPPlArlpIZGjkxgwHQ2DDlQdn4diqZ11FMzvtlJgMzXHTFtJUEgM8jAMldbuvXIy8HVcSxZbcQtr8JI3AMUj+hWmc/IOUENrjIaS7VX+CQ8kYzhmG5wo8zy6q4e10IuvQx9b64Ez3nQKa2qjuemMwldUBklkLNueZC3S/Nal3ahCft1GxOvZU+h+y1xontdyMmT5kAT2dnUwAEAHcBAAAAAACKvWM5BQAAAIzU2MIFqJuamVX+tXxtK7ebD71o+PA+NeOoAAQgvVR0EoN1CkCaAAgAjKf38nSNf4u3ag/+XpFtEq+zjyPPsXu95V9cnFU4vyRGvULIZtAGxTu7yORUhDRxQTxlWh6olMzB41ATLTnSIa0CqEMWv8nu3s/XgwnvyD6iqRUsAz55czD9d/r6pdnqfi0D02+LESrCpcvRlQnhZd3m/sVEdhvJWAnzJ2b9a2800lpFMjsWgAcetvy4FF61Lwia56U2HE1GQy05IRgAUIoKrhN1dtPIZ/JY2kFuhzJe3+u2sdgTd+Q1IFSGn7MWwem43ZaT5sZ2xIgr8SIH4ld0VWuFUbivk60RqbK8CjYutHhQDuyhWPl15GZXAifUfWBrHec0bFE6zfCPi+fTE9dYg648GjvMqfPGwPA7Kh81ViKXyJadJMVIfmntWjCbYXwAJh62fF8qr5oPWdY8l6hmLCXDUA46RjAAm7kaHwMP07gOXzlXpNBUqlxvcl7LuQuUE4kA9qgxwGm4Btpgd4c0BUqqN3rLhLi4Fh2iHJo0R9dbrpvN5vTqrHgesjvVJNBSG9TsqYlazlTVi8hUhzH2mON53LWD+koH815UYhq2idnRioq5p2jTyIvIDXbs56Pn1Yk2tVpTtGOeAAD+tXxuR27BByhQK470Mso4MQMAGEDRVH4pyLLa2oardDJo/m/O76V8dmdDqkje1KIzMptBvZVpW14S5nnioc2YetqFDsrKeRmMw9pPsz+LMk9xTEgmqWm6mEbccg4Wl3Bra+mtqGmD5rhii5av6sqrckDVZD7LbPNwVDy3pGdt1WnYg+vJ3III5C2FmgLcA2YwFnL26zA+OwAetvzvKLz4DnhDTRGEXBSCAEMwALDpQ5V3mE8BS8svH/DxwWFSgNlawtKa8+7MLtOQqyi40uCB+fxwlhW2YIl415T49BvgzgBDgEcVJndabjX4wSYA", + "volume": 100 + }, + "deafen": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/e4d539271704b87764dc465b1a061abd.mp3", + "volume": 100 + }, + "mute": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/429d09ee3b86e81a75b5e06d3fb482be.mp3", + "volume": 100 + }, + "disconnect": { + "category": "Discord", + "focus": null, + "mute": false, + "song": "Push2Talk Stop", + "src": "/assets/74ab980d6890a0fa6aa0336182f9f620.mp3", + "volume": 100 + }, + "undeafen": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/5a000a0d4dff083d12a1d4fc2c7cbf66.mp3", + "volume": 100 + }, + "unmute": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/43805b9dd757ac4f6b9b58c1a8ee5f0d.mp3", + "volume": 100 + }, + "user_join": { + "category": "Discord", + "focus": null, + "mute": false, + "song": "Push2Talk Start", + "src": "/assets/8b63833c8d252fedba6b9c4f2517c705.mp3", + "volume": 100 + }, + "user_leave": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/4fcfeb2cba26459c4750e60f626cebdc.mp3", + "volume": 100 + }, + "user_moved": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/e81d11590762728c1b811eadfa5be766.mp3", + "volume": 100 + }, + "reconnect": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/471cfd0005b112ff857705e894bf41a6.mp3", + "volume": 100 + }, + "ptt_start": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/8b63833c8d252fedba6b9c4f2517c705.mp3", + "volume": 100 + }, + "ptt_stop": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/74ab980d6890a0fa6aa0336182f9f620.mp3", + "volume": 100 + }, + "call_calling": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/c6e92752668dde4eee5923d70441579f.mp3", + "volume": 100 + }, + "call_ringing": { + "category": "Discord", + "focus": null, + "mute": true, + "song": "Incoming Call Beat", + "src": "/assets/b9411af07f154a6fef543e7e442e4da9.mp3", + "volume": 54.5 + }, + "call_ringing_beat": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/b9411af07f154a6fef543e7e442e4da9.mp3", + "volume": 100 + }, + "stream_started": { + "category": "Discord", + "focus": null, + "mute": false, + "song": "Unknown", + "src": "/assets/ae7d16bb2eea76b9b9977db0fad66658.mp3", + "volume": 100 + }, + "stream_ended": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/4e30f98aa537854f79f49a76af822bbc.mp3", + "volume": 100 + }, + "stream_user_joined": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/5827bbf9a67c61cbb0e02ffbf434b654.mp3", + "volume": 100 + }, + "stream_user_left": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/7cdcdcbc426cc43583365a671c24b740.mp3", + "volume": 100 + }, + "ddr-down": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/71f048f8aa7d4b24bf4268a87cbbb192.mp3", + "volume": 100 + }, + "ddr-left": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/1de04408e62b5d52ae3ebbb91e9e1978.mp3", + "volume": 100 + }, + "ddr-right": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/2c0433f93db8449e4a82b76dc520cb29.mp3", + "volume": 100 + }, + "ddr-up": { + "category": "---", + "focus": null, + "mute": false, + "song": "---", + "src": "/assets/68472713f7a62c7c37e0a6a5d5a1faeb.mp3", + "volume": 100 + }, + "mention1": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/fa4d62c3cbc80733bf1f01b9c6f181de.mp3", + "volume": 100 + }, + "mention2": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/a5f42064e8120e381528b14fd3188b72.mp3", + "volume": 100 + }, + "mention3": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/84c9fa3d07da865278bd77c97d952db4.mp3", + "volume": 100 + }, + "message2": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/15fe810f6cfab609c7fcda61652b9b34.mp3", + "volume": 100 + }, + "message3": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/53ce6a92d3c233e8b4ac529d34d374e4.mp3", + "volume": 100 + }, + "human_man": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/a37dcd6272ae41cf49295d58c9806fe3.mp3", + "volume": 100 + }, + "robot_man": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/66598bea6e59eb8acdf32cf2d9d75ba9.mp3", + "volume": 100 + }, + "discodo": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/ae7d16bb2eea76b9b9977db0fad66658.mp3", + "volume": 100 + }, + "overlayunlock": { + "category": "---", + "focus": null, + "mute": true, + "song": "---", + "src": "/assets/ad322ffe0a88436296158a80d5d11baa.mp3", + "volume": 100 + } + } +} \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/NotificationSounds.plugin.js b/.config/BetterDiscord/plugins/NotificationSounds.plugin.js new file mode 100644 index 0000000..8e48187 --- /dev/null +++ b/.config/BetterDiscord/plugins/NotificationSounds.plugin.js @@ -0,0 +1,546 @@ +//META{"name":"NotificationSounds","authorId":"278543574059057154","invite":"Jx3TjNS","donate":"https://www.paypal.me/MircoWittrien","patreon":"https://www.patreon.com/MircoWittrien","website":"https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/NotificationSounds","source":"https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/NotificationSounds/NotificationSounds.plugin.js"}*// + +var NotificationSounds = (_ => { + var audios, choices, firedEvents, repatchIncoming, callAudio; + + const settingsAudio = new Audio(); + + /* NEVER CHANGE THE SRC LINKS IN THE PLUGIN FILE, TO ADD NEW SONGS ADD THEM IN THE SETTINGS GUI IN THE PLUGINS PAGE */ + const types = { + "message1": {implemented:true, name:"New Chatmessage", src:"/assets/dd920c06a01e5bb8b09678581e29d56f.mp3", mute:true, focus:null, include:true}, + "dm": {implemented:true, name:"Direct Message", src:"/assets/84c9fa3d07da865278bd77c97d952db4.mp3", mute:true, focus:true, include:false}, + "mentioned": {implemented:true, name:"Mentioned", src:"/assets/a5f42064e8120e381528b14fd3188b72.mp3", mute:true, focus:true, include:false}, + "role": {implemented:true, name:"Mentioned (role)", src:"/assets/a5f42064e8120e381528b14fd3188b72.mp3", mute:true, focus:true, include:false}, + "everyone": {implemented:true, name:"Mentioned (@everyone)", src:"/assets/a5f42064e8120e381528b14fd3188b72.mp3", mute:true, focus:true, include:false}, + "here": {implemented:true, name:"Mentioned (@here)", src:"/assets/a5f42064e8120e381528b14fd3188b72.mp3", mute:true, focus:true, include:false}, + "deafen": {implemented:true, name:"Voicechat Deafen", src:"/assets/e4d539271704b87764dc465b1a061abd.mp3", mute:false, focus:null, include:true}, + "mute": {implemented:true, name:"Voicechat Mute", src:"/assets/429d09ee3b86e81a75b5e06d3fb482be.mp3", mute:false, focus:null, include:true}, + "disconnect": {implemented:true, name:"Voicechat Disconnect", src:"/assets/7e125dc075ec6e5ae796e4c3ab83abb3.mp3", mute:false, focus:null, include:true}, + "undeafen": {implemented:true, name:"Voicechat Undeafen", src:"/assets/5a000a0d4dff083d12a1d4fc2c7cbf66.mp3", mute:false, focus:null, include:true}, + "unmute": {implemented:true, name:"Voicechat Unmute", src:"/assets/43805b9dd757ac4f6b9b58c1a8ee5f0d.mp3", mute:false, focus:null, include:true}, + "user_join": {implemented:true, name:"Voicechat User Joined", src:"/assets/5dd43c946894005258d85770f0d10cff.mp3", mute:false, focus:null, include:true}, + "user_leave": {implemented:true, name:"Voicechat User Left", src:"/assets/4fcfeb2cba26459c4750e60f626cebdc.mp3", mute:false, focus:null, include:true}, + "user_moved": {implemented:true, name:"Voicechat User Moved", src:"/assets/e81d11590762728c1b811eadfa5be766.mp3", mute:false, focus:null, include:true}, + "reconnect": {implemented:false, name:"Voicechat Reconnect", src:"/assets/471cfd0005b112ff857705e894bf41a6.mp3", mute:true, focus:null, include:true}, + "ptt_start": {implemented:true, name:"Push2Talk Start", src:"/assets/8b63833c8d252fedba6b9c4f2517c705.mp3", mute:false, focus:null, include:true}, + "ptt_stop": {implemented:true, name:"Push2Talk Stop", src:"/assets/74ab980d6890a0fa6aa0336182f9f620.mp3", mute:false, focus:null, include:true}, + "call_calling": {implemented:true, name:"Outgoing Call", src:"/assets/c6e92752668dde4eee5923d70441579f.mp3", mute:false, focus:null, include:true}, + "call_ringing": {implemented:true, name:"Incoming Call", src:"/assets/84a1b4e11d634dbfa1e5dd97a96de3ad.mp3", mute:true, focus:null, include:true}, + "call_ringing_beat": {implemented:false, name:"Incoming Call Beat", src:"/assets/b9411af07f154a6fef543e7e442e4da9.mp3", mute:true, focus:null, include:true}, + "stream_started": {implemented:true, name:"Stream Started", src:"/assets/9ca817f41727edc1b2f1bc4f1911107c.mp3", mute:false, focus:null, include:true}, + "stream_ended": {implemented:true, name:"Stream Ended", src:"/assets/4e30f98aa537854f79f49a76af822bbc.mp3", mute:false, focus:null, include:true}, + "stream_user_joined": {implemented:true, name:"Stream User Joined", src:"/assets/5827bbf9a67c61cbb0e02ffbf434b654.mp3", mute:false, focus:null, include:true}, + "stream_user_left": {implemented:true, name:"Stream User Left", src:"/assets/7cdcdcbc426cc43583365a671c24b740.mp3", mute:false, focus:null, include:true}, + "ddr-down": {implemented:true, name:"HotKeys Window Down", src:"/assets/71f048f8aa7d4b24bf4268a87cbbb192.mp3", mute:false, focus:null, include:true}, + "ddr-left": {implemented:true, name:"HotKeys Window Left", src:"/assets/1de04408e62b5d52ae3ebbb91e9e1978.mp3", mute:false, focus:null, include:true}, + "ddr-right": {implemented:true, name:"HotKeys Window Right", src:"/assets/2c0433f93db8449e4a82b76dc520cb29.mp3", mute:false, focus:null, include:true}, + "ddr-up": {implemented:true, name:"HotKeys Window Up", src:"/assets/68472713f7a62c7c37e0a6a5d5a1faeb.mp3", mute:false, focus:null, include:true}, + "mention1": {implemented:false, name:"Mention Ping", src:"/assets/fa4d62c3cbc80733bf1f01b9c6f181de.mp3", mute:true, focus:null, include:true}, + "mention2": {implemented:false, name:"Mention Ping 2", src:"/assets/a5f42064e8120e381528b14fd3188b72.mp3", mute:true, focus:null, include:true}, + "mention3": {implemented:false, name:"Mention Ping 3", src:"/assets/84c9fa3d07da865278bd77c97d952db4.mp3", mute:true, focus:null, include:true}, + "message2": {implemented:false, name:"New Chatmessage 2", src:"/assets/15fe810f6cfab609c7fcda61652b9b34.mp3", mute:true, focus:null, include:true}, + "message3": {implemented:false, name:"New Chatmessage 3", src:"/assets/53ce6a92d3c233e8b4ac529d34d374e4.mp3", mute:true, focus:null, include:true}, + "human_man": {implemented:false, name:"Human Man Voice", src:"/assets/a37dcd6272ae41cf49295d58c9806fe3.mp3", mute:true, focus:null, include:true}, + "robot_man": {implemented:false, name:"Robot Man Voice", src:"/assets/66598bea6e59eb8acdf32cf2d9d75ba9.mp3", mute:true, focus:null, include:true}, + "discodo": {implemented:false, name:"Unknown", src:"/assets/ae7d16bb2eea76b9b9977db0fad66658.mp3", mute:true, focus:null, include:true}, + "overlayunlock": {implemented:false, name:"Overlay Unlocked", src:"/assets/ad322ffe0a88436296158a80d5d11baa.mp3", mute:true, focus:null, include:true} + }; + + /* NEVER CHANGE THE SRC LINKS IN THE PLUGIN FILE, TO ADD NEW SONGS ADD THEM IN THE SETTINGS GUI IN THE PLUGINS PAGE */ + const defaultAudios = { + "---": { + "---": null + }, + "Default": { + "Communication Channel": "https://notificationsounds.com/soundfiles/63538fe6ef330c13a05a3ed7e599d5f7/file-sounds-917-communication-channel.wav", + "Isn't it": "https://notificationsounds.com/soundfiles/ba2fd310dcaa8781a9a652a31baf3c68/file-sounds-969-isnt-it.wav", + "Job Done": "https://notificationsounds.com/soundfiles/5b69b9cb83065d403869739ae7f0995e/file-sounds-937-job-done.wav", + "Served": "https://notificationsounds.com/soundfiles/b337e84de8752b27eda3a12363109e80/file-sounds-913-served.wav", + "Solemn": "https://notificationsounds.com/soundfiles/53fde96fcc4b4ce72d7739202324cd49/file-sounds-882-solemn.wav", + "System Fault": "https://notificationsounds.com/soundfiles/ebd9629fc3ae5e9f6611e2ee05a31cef/file-sounds-990-system-fault.wav", + "You wouldn't believe": "https://notificationsounds.com/soundfiles/087408522c31eeb1f982bc0eaf81d35f/file-sounds-949-you-wouldnt-believe.wav" + }, + "Discord": {} + }; + + for (let id in types) if (types[id].include) defaultAudios.Discord[types[id].name] = types[id].src; + + return class NotificationSounds { + getName () {return "NotificationSounds";} + + getVersion () {return "3.4.5";} + + getAuthor () {return "DevilBro";} + + getDescription () {return "Allows you to replace the native sounds of Discord with your own";} + + constructor () { + this.changelog = { + "fixed":[["Mention Sound","No longer plays when the server/channel has message notifications completely disabled"]] + }; + + this.patchedModules = { + after: { + Shakeable: "render" + } + }; + } + + initConstructor () { + audios = {}; + choices = {}; + firedEvents = {}; + } + + getSettingsPanel (collapseStates = {}) { + if (!window.BDFDB || typeof BDFDB != "object" || !BDFDB.loaded || !this.started) return; + let settingsPanel = {node: null}, settingsItems = []; + + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Add new Song", + collapseStates: collapseStates, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.margintop4, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: "Categoryname", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + className: "input-newsong input-category", + value: "", + placeholder: "Categoryname" + }) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: "Songname", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + className: "input-newsong input-song", + value: "", + placeholder: "Songname" + }) + }) + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.margintop4, + align: BDFDB.LibraryComponents.Flex.Align.END, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: "Source", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + className: "input-newsong input-source", + type: "file", + filter: ["audio", "video"], + useFilePath: true, + value: "", + placeholder: "Source" + }) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, { + style: {marginBottom: 1}, + onClick: _ => { + for (let input of settingsPanel.node.querySelectorAll(".input-newsong " + BDFDB.dotCN.input)) if (!input.value || input.value.length == 0 || input.value.trim().length == 0) return BDFDB.NotificationUtils.toast("Fill out all fields to add a new song.", {type:"danger"}); + let category = settingsPanel.node.querySelector(".input-category " + BDFDB.dotCN.input).value.trim(); + let song = settingsPanel.node.querySelector(".input-song " + BDFDB.dotCN.input).value.trim(); + let source = settingsPanel.node.querySelector(".input-source " + BDFDB.dotCN.input).value.trim(); + if (source.indexOf("http") == 0) BDFDB.LibraryRequires.request(source, (error, response, result) => { + if (response) { + let type = response.headers["content-type"]; + if (type && (type.indexOf("octet-stream") > -1 || type.indexOf("audio") > -1 || type.indexOf("video") > -1)) return this.successSavedAudio(settingsPanel.node, collapseStates, {category, song, source}); + } + BDFDB.NotificationUtils.toast("Use a valid direct link to a video or audio source. They usually end on something like .mp3, .mp4 or .wav.", {type:"danger"}); + }); + else BDFDB.LibraryRequires.fs.readFile(source, (error, response) => { + if (error) BDFDB.NotificationUtils.toast("Could not fetch file. Please make sure the file exists.", {type:"danger"}); + else return this.successSavedAudio(settingsPanel.node, collapseStates, {category, song, source:`data:audio/mpeg;base64,${response.toString("base64")}`}); + }); + }, + children: BDFDB.LanguageUtils.LanguageStrings.SAVE + }) + ] + }) + ] + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Implemented Sounds", + collapseStates: collapseStates, + dividertop: true, + children: Object.keys(BDFDB.ObjectUtils.filter(types, typedata => typedata.implemented)).map(type => this.createSoundCard(type, settingsPanel, collapseStates)).flat(10).filter(n => n) + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Unimplemented Sounds", + collapseStates: collapseStates, + dividertop: true, + children: Object.keys(BDFDB.ObjectUtils.filter(types, typedata => !typedata.implemented)).map(type => this.createSoundCard(type, settingsPanel, collapseStates)).flat(10).filter(n => n) + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { + title: "Remove Songs", + collapseStates: collapseStates, + dividertop: true, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Button", + className: BDFDB.disCN.marginbottom8, + color: BDFDB.LibraryComponents.Button.Colors.RED, + label: "Delete all added songs", + onClick: _ => { + BDFDB.ModalUtils.confirm(this, "Are you sure you want to delete all added songs?", _ => { + BDFDB.DataUtils.remove(this, "choices"); + BDFDB.DataUtils.remove(this, "audios"); + this.loadAudios(); + this.loadChoices(); + BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel.node, collapseStates); + }); + }, + children: BDFDB.LanguageUtils.LanguageStrings.DELETE + }) + })); + + return settingsPanel.node = BDFDB.PluginUtils.createSettingsPanel(this, settingsItems); + } + + createSoundCard (type, settingsPanel, collapseStates) { + return [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.marginbottom8, + align: BDFDB.LibraryComponents.Flex.Align.CENTER, + direction: BDFDB.LibraryComponents.Flex.Direction.HORIZONTAL, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsLabel, { + label: types[type].name + }), + types[type].focus != null ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Switch", + mini: true, + grow: 0, + label: "Mute when Channel focused:", + value: choices[type].focus, + onChange: value => { + choices[type].focus = value; + this.saveChoice(type, false); + } + }) : null, + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Switch", + mini: true, + grow: 0, + label: "Mute in DnD:", + value: choices[type].mute, + onChange: value => { + choices[type].mute = value; + this.saveChoice(type, false); + } + }) + ].filter(n => n) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { + className: BDFDB.disCN.marginbottom8, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 0, + basis: "31%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: "Category", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, { + value: choices[type].category, + options: Object.keys(audios).map(name => {return {value:name, label:name}}), + searchable: true, + onChange: category => { + choices[type].category = category.value; + choices[type].song = Object.keys(audios[category.value] || {})[0]; + choices[type].src = audios[choices[type].category][choices[type].song] || types[type].src; + this.saveChoice(type, true); + BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel.node, collapseStates); + } + }) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 0, + basis: "31%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: "Song", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, { + value: choices[type].song, + options: Object.keys(audios[choices[type].category] || {}).map(name => {return {value:name, label:name}}), + searchable: true, + onChange: song => { + choices[type].song = song.value; + choices[type].src = audios[choices[type].category][choices[type].song] || types[type].src; + this.saveChoice(type, true); + BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel.node, collapseStates); + } + }) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { + grow: 0, + shrink: 0, + basis: "31%", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: "Volume", + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Slider, { + defaultValue: choices[type].volume, + digits: 1, + onValueRender: value => { + return value + "%"; + }, + onValueChange: value => { + choices[type].volume = value; + this.saveChoice(type, true); + } + }) + }) + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, { + className: BDFDB.disCN.marginbottom8 + }) + ]; + } + + // Legacy + load () {} + + start () { + if (!window.BDFDB) window.BDFDB = {myPlugins:{}}; + if (window.BDFDB && window.BDFDB.myPlugins && typeof window.BDFDB.myPlugins == "object") window.BDFDB.myPlugins[this.getName()] = this; + let libraryScript = document.querySelector("head script#BDFDBLibraryScript"); + if (!libraryScript || (performance.now() - libraryScript.getAttribute("date")) > 600000) { + if (libraryScript) libraryScript.remove(); + libraryScript = document.createElement("script"); + libraryScript.setAttribute("id", "BDFDBLibraryScript"); + libraryScript.setAttribute("type", "text/javascript"); + libraryScript.setAttribute("src", "https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.min.js"); + libraryScript.setAttribute("date", performance.now()); + libraryScript.addEventListener("load", _ => {this.initialize();}); + document.head.appendChild(libraryScript); + } + else if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) this.initialize(); + this.startTimeout = setTimeout(_ => { + try {return this.initialize();} + catch (err) {console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not initiate plugin! " + err);} + }, 30000); + } + + initialize () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + if (this.started) return; + BDFDB.PluginUtils.init(this); + + BDFDB.ModuleUtils.patch(this, BDFDB.LibraryModules.DispatchApiUtils, "dirtyDispatch", {before: e => { + if (BDFDB.ObjectUtils.is(e.methodArguments[0]) && e.methodArguments[0].type == BDFDB.DiscordConstants.ActionTypes.MESSAGE_CREATE && e.methodArguments[0].message) { + let message = e.methodArguments[0].message; + let guildId = message.guild_id || null; + if (!BDFDB.LibraryModules.MutedUtils.isGuildOrCategoryOrChannelMuted(guildId, message.channel_id) && message.author.id != BDFDB.UserUtils.me.id && !BDFDB.LibraryModules.FriendUtils.isBlocked(message.author.id)) { + if (!guildId && !(choices.dm.focus && document.hasFocus() && BDFDB.LibraryModules.LastChannelStore.getChannelId() == message.channel_id)) { + this.fireEvent("dm"); + this.playAudio("dm"); + return; + } + else if (BDFDB.LibraryModules.MentionUtils.isRawMessageMentioned(message, BDFDB.UserUtils.me.id)) { + if (message.mentions.length && !this.isSuppressMentionEnabled(guildId, message.channel_id) && !(choices.mentioned.focus && document.hasFocus() && BDFDB.LibraryModules.LastChannelStore.getChannelId() == message.channel_id)) for (let mention of message.mentions) if (mention.id == BDFDB.UserUtils.me.id) { + this.fireEvent("mentioned"); + this.playAudio("mentioned"); + return; + } + if (guildId && message.mention_roles.length && !BDFDB.LibraryModules.MutedUtils.isSuppressRolesEnabled(guildId, message.channel_id) && !(choices.role.focus && document.hasFocus() && BDFDB.LibraryModules.LastChannelStore.getChannelId() == message.channel_id)) { + let member = BDFDB.LibraryModules.MemberStore.getMember(guildId, BDFDB.UserUtils.me.id); + if (member && member.roles.length) for (let roleId of message.mention_roles) if (member.roles.includes(roleId)) { + this.fireEvent("role"); + this.playAudio("role"); + return; + } + } + if (message.mention_everyone && !BDFDB.LibraryModules.MutedUtils.isSuppressEveryoneEnabled(guildId, message.channel_id)) { + if (message.content.indexOf("@everyone") > -1 && !(choices.everyone.focus && document.hasFocus() && BDFDB.LibraryModules.LastChannelStore.getChannelId() == message.channel_id)) { + this.fireEvent("everyone"); + this.playAudio("everyone"); + return; + } + if (message.content.indexOf("@here") > -1 && !(choices.here.focus && document.hasFocus() && BDFDB.LibraryModules.LastChannelStore.getChannelId() == message.channel_id)) { + this.fireEvent("here"); + this.playAudio("here"); + return; + } + } + } + } + } + }}); + + BDFDB.ModuleUtils.patch(this, BDFDB.LibraryModules.SoundUtils, "playSound", {instead: e => { + let type = e.methodArguments[0]; + if (choices[type]) BDFDB.TimeUtils.timeout(_ => { + if (type == "message1") { + if (firedEvents["dm"]) firedEvents["dm"] = false; + else if (firedEvents["mentioned"]) firedEvents["mentioned"] = false; + else if (firedEvents["role"]) firedEvents["role"] = false; + else if (firedEvents["everyone"]) firedEvents["everyone"] = false; + else if (firedEvents["here"]) firedEvents["here"] = false; + else this.playAudio(type); + } + else this.playAudio(type); + }); + else e.callOriginalMethod(); + }}); + BDFDB.ModuleUtils.patch(this, BDFDB.LibraryModules.SoundUtils, "createSound", {after: e => { + let type = e.methodArguments[0]; + let audio = new Audio(); + audio.src = choices[type].src; + audio.volume = choices[type].volume/100; + e.returnValue.play = _ => { + if (!audio.paused || this.dontPlayAudio(type)) return; + audio.loop = false; + audio.play(); + }; + e.returnValue.loop = _ => { + if (!audio.paused || this.dontPlayAudio(type)) return; + audio.loop = true; + audio.play(); + }; + e.returnValue.stop = _ => {audio.pause();} + }}); + + + for (let key in defaultAudios) defaultAudios[key] = BDFDB.ObjectUtils.sort(defaultAudios[key]); + + this.loadAudios(); + this.loadChoices(); + + let callListenerModule = BDFDB.ModuleUtils.findByProperties("handleRingUpdate"); + if (callListenerModule) { + callListenerModule.terminate(); + BDFDB.ModuleUtils.patch(this, callListenerModule, "handleRingUpdate", {instead: e => { + BDFDB.LibraryModules.CallUtils.getCalls().filter(call => call.ringing.length > 0 && BDFDB.LibraryModules.VoiceUtils.getCurrentClientVoiceChannelId() === call.channelId).length > 0 && !BDFDB.LibraryModules.SoundStateUtils.isSoundDisabled("call_calling") && !BDFDB.LibraryModules.StreamerModeStore.disableSounds ? callAudio.loop() : callAudio.stop(); + }}); + callListenerModule.initialize(); + } + + this.forceUpdateAll(); + } + else console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not load BD functions!"); + } + + + stop () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + this.stopping = true; + + BDFDB.PluginUtils.clear(this); + settingsAudio.pause(); + } + } + + + // Begin of own functions + + onSettingsClosed () { + if (this.SettingsUpdated) { + delete this.SettingsUpdated; + settingsAudio.pause(); + + this.forceUpdateAll(); + } + } + + processShakeable (e) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {name: "IncomingCalls"}); + if (index > -1) { + if (repatchIncoming) { + children[index] = null; + BDFDB.TimeUtils.timeout(_ => { + repatchIncoming = false; + BDFDB.ReactUtils.forceUpdate(BDFDB.ReactUtils.findOwner(document.querySelector(BDFDB.dotCN.app), {name:"App", up:true})) + }); + } + else children[index] = BDFDB.ReactUtils.createElement(children[index].type, {}); + } + } + + successSavedAudio (settingsPanel, collapseStates, data) { + BDFDB.NotificationUtils.toast(`Song ${data.song} was added to category ${data.category}.`, {type:"success"}); + if (!audios[data.category]) audios[data.category] = {}; + audios[data.category][data.song] = data.source; + BDFDB.DataUtils.save(audios, this, "audios"); + BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); + + } + + forceUpdateAll () { + repatchIncoming = true; + callAudio = BDFDB.LibraryModules.SoundUtils.createSound("call_calling"); + + BDFDB.ModuleUtils.forceAllUpdates(this); + } + + loadAudios () { + audios = Object.assign({}, defaultAudios, BDFDB.DataUtils.load(this, "audios")); + BDFDB.DataUtils.save(audios, this, "audios"); + } + + loadChoices () { + let loadedChoices = BDFDB.DataUtils.load(this, "choices"); + for (let type in types) { + let choice = loadedChoices[type] || {}, songFound = false; + for (let category in audios) if (choice.category == category) for (let song in audios[category]) if (choice.song == song) { + songFound = true; + break; + } + if (!songFound) choice = { + category: "---", + song: "---", + volume: 100, + src: types[type].src, + mute: types[type].mute, + focus: types[type].focus + }; + choices[type] = choice; + this.saveChoice(type, false); + } + } + + saveChoice (type, play) { + if (!choices[type]) return; + BDFDB.DataUtils.save(choices[type], this, "choices", type); + if (play) { + this.SettingsUpdated = true; + this.playAudio(type, settingsAudio); + } + } + + playAudio (type, audio) { + if (!audio) { + if (this.dontPlayAudio(type)) return; + audio = new Audio(); + } + else audio.pause(); + audio.src = choices[type].src; + audio.volume = choices[type].volume/100; + audio.play(); + } + + isSuppressMentionEnabled (guildId, channelId) { + let channelSettings = BDFDB.LibraryModules.MutedUtils.getChannelMessageNotifications(guildId, channelId); + return channelSettings && (channelSettings == BDFDB.DiscordConstants.UserNotificationSettings.NO_MESSAGES || channelSettings == BDFDB.DiscordConstants.UserNotificationSettings.NULL && BDFDB.LibraryModules.MutedUtils.getMessageNotifications(guildId) == BDFDB.DiscordConstants.UserNotificationSettings.NO_MESSAGES); + } + + dontPlayAudio (type) { + let status = BDFDB.UserUtils.getStatus(); + return choices[type].mute && (status == "dnd" || status == "streaming"); + } + + fireEvent (type) { + firedEvents[type] = true; + BDFDB.TimeUtils.timeout(_ => {firedEvents[type] = false;},3000); + } + } +})(); \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/PinDMs.config.json b/.config/BetterDiscord/plugins/PinDMs.config.json new file mode 100644 index 0000000..63542a6 --- /dev/null +++ b/.config/BetterDiscord/plugins/PinDMs.config.json @@ -0,0 +1,40 @@ +{ + "changelog": { + "currentversion": "1.7.2" + }, + "dmCategories": { + "3516090637537661": { + "collapsed": false, + "color": null, + "dms": [ + "456428001463369729", + "390601823523962892", + "450987439041806346", + "390786176652673034" + ], + "id": "3516090637537661", + "name": "Gouden mannen", + "pos": 1 + }, + "3586821958853646": { + "collapsed": false, + "color": null, + "dms": [ + "606832163404644352", + "590888637877452840", + "458302680998215690", + "514803935761006592" + ], + "id": "3586821958853646", + "name": "Jongens", + "pos": 0 + } + }, + "settings": { + "showCategoryAmount": false, + "showCategoryUnread": true, + "showPinIcon": true, + "sortInRecentOrder": false, + "sortInRecentOrderGuild": false + } +} \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/PinDMs.plugin.js b/.config/BetterDiscord/plugins/PinDMs.plugin.js new file mode 100644 index 0000000..4c5b002 --- /dev/null +++ b/.config/BetterDiscord/plugins/PinDMs.plugin.js @@ -0,0 +1,1149 @@ +//META{"name":"PinDMs","authorId":"278543574059057154","invite":"Jx3TjNS","donate":"https://www.paypal.me/MircoWittrien","patreon":"https://www.patreon.com/MircoWittrien","website":"https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/PinDMs","source":"https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/PinDMs/PinDMs.plugin.js"}*// + +var PinDMs = (_ => { + let hoveredCategory, draggedCategory, releasedCategory; + let hoveredChannel, draggedChannel, releasedChannel; + + return class PinDMs { + getName () {return "PinDMs";} + + getVersion () {return "1.7.2";} + + getAuthor () {return "DevilBro";} + + getDescription () {return "Allows you to pin DMs, making them appear at the top of your DMs/Guild-list.";} + + constructor () { + this.changelog = { + "fixed":[["Context Menu Update","Fixes for the context menu update, yaaaaaay"]] + }; + + this.patchedModules = { + before: { + PrivateChannelsList: "render", + UnreadDMs: "render" + }, + after: { + PrivateChannelsList: "render", + UnreadDMs: "render", + PrivateChannel: ["render", "componentDidMount"], + DirectMessage: ["render", "componentDidMount", "componentWillUnmount"] + } + }; + } + + initConstructor () { + this.css = ` + ${BDFDB.dotCNS.dmchannel + BDFDB.dotCN.namecontainerchildren} { + display: flex; + } + ${BDFDB.dotCN.dmchannel}:hover ${BDFDB.dotCN._pindmsunpinbutton} { + display: block; + } + ${BDFDB.dotCN._pindmspinnedchannelsheadercontainer} { + display: flex; + cursor: pointer; + } + ${BDFDB.dotCNS._pindmspinnedchannelsheadercontainer + BDFDB.dotCN.dmchannelheadertext} { + margin-right: 6px; + } + ${BDFDB.dotCN._pindmspinnedchannelsheadercontainer + BDFDB.dotCN._pindmspinnedchannelsheadercolored}:hover ${BDFDB.dotCN.dmchannelheadertext} { + filter: brightness(150%); + } + ${BDFDB.dotCNS._pindmspinnedchannelsheadercontainer + BDFDB.dotCN._pindmspinnedchannelsheaderamount} { + position: relative; + top: -1px; + margin-right: 6px; + } + ${BDFDB.dotCN._pindmspinnedchannelsheaderarrow} { + flex: 0; + width: 16px; + height: 16px; + margin-left: 0; + margin-right: 2px; + } + ${BDFDB.dotCNS._pindmspinnedchannelsheadercollapsed + BDFDB.dotCN._pindmspinnedchannelsheaderarrow + BDFDB.dotCN.channelheadericonwrapper} { + transform: rotate(-90deg); + } + ${BDFDB.dotCN._pindmsunpinbutton} { + display: none; + width: 16px; + height: 16px; + opacity: .7; + margin: 2px; + } + ${BDFDB.dotCN._pindmsunpinbutton}:hover { + opacity: 1; + } + ${BDFDB.dotCN._pindmsunpinicon} { + display: block; + width: 16px; + height: 16px; + } + ${BDFDB.dotCNS._pindmsdmchannelplaceholder + BDFDB.dotCN.namecontainerlayout} { + box-sizing: border-box; + border: 1px dashed currentColor; + } + ${BDFDB.dotCN._pindmspinnedchannelsheadercontainer + BDFDB.dotCN._pindmsdmchannelplaceholder} { + margin-left: 8px; + height: 12px; + box-sizing: border-box; + border: 1px dashed currentColor; + } + ${BDFDB.dotCN._pindmsdragpreview} { + pointer-events: none !important; + position: absolute !important; + opacity: 0.5 !important; + z-index: 10000 !important; + }`; + + this.defaults = { + settings: { + sortInRecentOrder: {value:false, inner:true, description:"Channel List"}, + sortInRecentOrderGuild: {value:false, inner:true, description:"Guild List"}, + showPinIcon: {value:true, inner:false, description:"Shows a little 'Pin' icon for pinned DMs in the server list:"}, + showCategoryUnread: {value:true, inner:false, description:"Shows the amount of unread Messages in a category in the channel list:"}, + showCategoryAmount: {value:true, inner:false, description:"Shows the amount of pinned DMs in a category in the channel list:"} + } + }; + } + + getSettingsPanel () { + if (!window.BDFDB || typeof BDFDB != "object" || !BDFDB.loaded || !this.started) return; + let settings = BDFDB.DataUtils.get(this, "settings"); + let settingsPanel, settingsItems = [], innerItems = []; + + for (let key in settings) (!this.defaults.settings[key].inner ? settingsItems : innerItems).push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + className: BDFDB.disCN.marginbottom8, + type: "Switch", + plugin: this, + keys: ["settings", key], + label: this.defaults.settings[key].description, + value: settings[key] + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelInner, { + title: "Sort pinned DMs in the recent message order instead of the pinned at order in:", + first: settingsItems.length == 0, + children: innerItems + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Button", + className: BDFDB.disCN.marginbottom8, + color: BDFDB.LibraryComponents.Button.Colors.RED, + label: "Unpin all pinned DMs", + onClick: _ => { + BDFDB.ModalUtils.confirm(this, "Are you sure you want to unpin all pinned DMs?", _ => { + BDFDB.DataUtils.remove(this, "dmCategories"); + BDFDB.DataUtils.remove(this, "pinnedRecents"); + }); + }, + children: BDFDB.LanguageUtils.LanguageStrings.UNPIN + })); + + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, settingsItems); + } + + // Legacy + load () {} + + start () { + if (!window.BDFDB) window.BDFDB = {myPlugins:{}}; + if (window.BDFDB && window.BDFDB.myPlugins && typeof window.BDFDB.myPlugins == "object") window.BDFDB.myPlugins[this.getName()] = this; + let libraryScript = document.querySelector("head script#BDFDBLibraryScript"); + if (!libraryScript || (performance.now() - libraryScript.getAttribute("date")) > 600000) { + if (libraryScript) libraryScript.remove(); + libraryScript = document.createElement("script"); + libraryScript.setAttribute("id", "BDFDBLibraryScript"); + libraryScript.setAttribute("type", "text/javascript"); + libraryScript.setAttribute("src", "https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.min.js"); + libraryScript.setAttribute("date", performance.now()); + libraryScript.addEventListener("load", _ => {this.initialize();}); + document.head.appendChild(libraryScript); + } + else if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) this.initialize(); + this.startTimeout = setTimeout(_ => { + try {return this.initialize();} + catch (err) {console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not initiate plugin! " + err);} + }, 30000); + } + + initialize () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + if (this.started) return; + BDFDB.PluginUtils.init(this); + + this.forceUpdateAll(); + } + else console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not load BD functions!"); + } + + stop () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + this.stopping = true; + + this.forceUpdateAll(true); + + let unreadDMsInstance = BDFDB.ReactUtils.findOwner(document.querySelector(BDFDB.dotCN.app), {name:"UnreadDMs", unlimited:true}); + if (unreadDMsInstance) { + delete unreadDMsInstance.props.pinnedPrivateChannelIds; + unreadDMsInstance.props.unreadPrivateChannelIds = BDFDB.LibraryModules.DirectMessageUnreadStore.getUnreadPrivateChannelIds(); + BDFDB.ReactUtils.forceUpdate(unreadDMsInstance); + } + + BDFDB.PluginUtils.clear(this); + } + } + + + // Begin of own functions + + onSettingsClosed (instance, wrapper, returnvalue) { + if (this.SettingsUpdated) { + delete this.SettingsUpdated; + this.forceUpdateAll(); + } + } + + onUserContextMenu (e) { + if (e.instance.props.user) { + let [children, index] = BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: "close-dm"}); + if (index > -1) { + let id = BDFDB.LibraryModules.ChannelStore.getDMFromUserId(e.instance.props.user.id); + if (id) this.injectItem(e.instance, id, children, index); + } + } + } + + onGroupDMContextMenu (e) { + if (e.instance.props.channel) { + let [children, index] = BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: "change-icon"}); + if (index > -1) this.injectItem(e.instance, e.instance.props.channel.id, children, index + 1); + } + } + + injectItem (instance, id, children, index) { + let pinnedInGuild = this.isPinned(id, "pinnedRecents"); + + let categories = this.sortAndUpdateCategories("dmCategories", true); + let currentCategory = this.getCategory(id, "dmCategories"); + + children.splice(index, 0, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels.context_pindm_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "submenu-pin"), + children: [ + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels.context_pinchannel_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "submenu-channelist"), + children: [ + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: currentCategory ? BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels.context_unpinchannel_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "unpin-channellist"), + color: BDFDB.LibraryComponents.MenuItems.Colors.DANGER, + action: _ => { + BDFDB.ContextMenuUtils.close(instance); + this.removeFromCategory(id, currentCategory, "dmCategories"); + } + }) : BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels.context_addtonewcategory_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "new-channellist"), + color: BDFDB.LibraryComponents.MenuItems.Colors.BRAND, + action: _ => { + BDFDB.ContextMenuUtils.close(instance); + this.openCategorySettingsModal({ + id: this.generateID("dmCategories").toString(), + name: `${this.labels.header_pinneddms_text} #${categories.length + 1}`, + dms: [id], + pos: categories.length, + collapsed: false, + color: null + }, "dmCategories", true); + } + }) + }), + categories.length ? BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: categories.map(category => currentCategory && currentCategory.id == category.id ? null : BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: category.name || this.labels.header_pinneddms_text, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "pin-channellist", category.id), + action: _ => { + BDFDB.ContextMenuUtils.close(instance); + if (currentCategory) this.removeFromCategory(id, currentCategory, "dmCategories"); + this.addToCategory(id, category, "dmCategories"); + } + })).filter(n => n) + }) : null + ].filter(n => n) + }), + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: this.labels[pinnedInGuild ? "context_unpinguild_text" : "context_pinguild_text"], + id: BDFDB.ContextMenuUtils.createItemId(this.name, pinnedInGuild ? "unpin-serverlist" : "pin-serverlist"), + danger: pinnedInGuild, + action: _ => { + BDFDB.ContextMenuUtils.close(instance); + if (!pinnedInGuild) this.addPin(id, "pinnedRecents"); + else this.removePin(id, "pinnedRecents"); + } + }) + ] + })); + } + + processPrivateChannelsList (e) { + let categories = this.sortAndUpdateCategories("dmCategories", true); + if (categories.length) { + e.instance.props.channels = Object.assign({}, e.instance.props.channels); + e.instance.props.privateChannelIds = [].concat(e.instance.props.privateChannelIds || []); + e.instance.props.pinnedChannelIds = Object.assign({}, e.instance.props.pinnedChannelIds); + if (!e.returnvalue) { + if (draggedChannel && releasedChannel) { + let categoryId = releasedChannel.split("header_")[1]; + let category = categories.find(n => categoryId != undefined ? n.id == categoryId : n.dms.includes(releasedChannel)); + if (category) { + BDFDB.ArrayUtils.remove(category.dms, draggedChannel, true); + category.dms.splice(categoryId != undefined ? 0 : category.dms.indexOf(releasedChannel) + 1, 0, draggedChannel); + BDFDB.DataUtils.save(category, this, "dmCategories", category.id); + } + draggedChannel = null; + releasedChannel = null; + } + if (draggedCategory && releasedCategory) { + let maybedDraggedCategory = categories.find(n => n.id == draggedCategory); + let maybedReleasedCategory = categories.find(n => n.id == releasedCategory); + if (maybedDraggedCategory && maybedReleasedCategory) { + BDFDB.ArrayUtils.remove(categories, maybedDraggedCategory, true); + categories.splice(categories.indexOf(maybedReleasedCategory) + 1, 0, maybedDraggedCategory); + let newCategories = {}, newPos = 0; + for (let category of [].concat(categories).reverse()) newCategories[category.id] = Object.assign(category, {pos:newPos++}); + BDFDB.DataUtils.save(newCategories, this, "dmCategories"); + } + draggedCategory = null; + releasedCategory = null; + } + e.instance.props.pinnedChannelIds = {}; + for (let category of [].concat(categories).reverse()) { + e.instance.props.pinnedChannelIds[category.id] = []; + for (let id of this.sortDMsByTime(this.filterDMs(category.dms), "dmCategories").reverse()) { + BDFDB.ArrayUtils.remove(e.instance.props.privateChannelIds, id, true); + if (!category.collapsed || e.instance.props.selectedChannelId == id) { + e.instance.props.privateChannelIds.unshift(id); + e.instance.props.pinnedChannelIds[category.id].push(id); + } + } + } + } + else { + e.returnvalue.props.sections = []; + e.returnvalue.props.sections.push(e.instance.state.preRenderedChildren); + let shownPinnedIds = BDFDB.ObjectUtils.toArray(e.instance.props.pinnedChannelIds).reverse(); + for (let ids of shownPinnedIds) e.returnvalue.props.sections.push(ids.length || 1); + e.returnvalue.props.sections.push(e.instance.props.privateChannelIds.length - shownPinnedIds.flat().length); + + let sectionHeight = e.returnvalue.props.sectionHeight; + let sectionHeightFunc = typeof sectionHeight != "function" ? _ => sectionHeight : sectionHeight; + e.returnvalue.props.sectionHeight = (...args) => { + if (args[0] != 0 && args[0] != e.returnvalue.props.sections.length - 1) { + let category = categories[args[0] - 1]; + if (category) return 40; + } + return sectionHeightFunc(...args); + }; + + let rowHeight = e.returnvalue.props.rowHeight; + let rowHeightFunc = typeof rowHeight != "function" ? _ => rowHeight : rowHeight; + e.returnvalue.props.rowHeight = (...args) => { + if (args[0] != 0 && args[0] != e.returnvalue.props.sections.length - 1) { + let category = categories[args[0] - 1]; + if (category && (category.collapsed || category.id == draggedCategory)) return 0; + } + return rowHeightFunc(...args); + }; + } + + let settings = BDFDB.DataUtils.get(this, "settings"); + BDFDB.ModuleUtils.unpatch(this, e.instance, "renderSection"); + BDFDB.ModuleUtils.patch(this, e.instance, "renderSection", {after: e2 => { + if (e2.methodArguments[0].section != 0 && e2.methodArguments[0].section != e.instance.props.listRef.current.props.sections.length - 1) { + let category = categories[e2.methodArguments[0].section - 1]; + if (category && draggedCategory != category.id) { + let color = BDFDB.ColorUtils.convert(category.color, "RGBA"); + let foundDMs = this.filterDMs(category.dms); + let unreadAmount = settings.showCategoryUnread && BDFDB.ArrayUtils.sum(foundDMs.map(id => BDFDB.LibraryModules.UnreadChannelUtils.getMentionCount(id))); + e2.returnValue = [ + BDFDB.ReactUtils.createElement("h2", { + className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.dmchannelheadercontainer, BDFDB.disCN._pindmspinnedchannelsheadercontainer, category.collapsed && BDFDB.disCN._pindmspinnedchannelsheadercollapsed, color && BDFDB.disCN._pindmspinnedchannelsheadercolored, BDFDB.disCN.namecontainernamecontainer), + categoryId: category.id, + onMouseDown: event => { + event = event.nativeEvent || event; + let node = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmspinnedchannelsheadercontainer, event.target).cloneNode(true); + let mousemove = event2 => { + if (Math.sqrt((event.pageX - event2.pageX)**2) > 20 || Math.sqrt((event.pageY - event2.pageY)**2) > 20) { + BDFDB.ListenerUtils.stopEvent(event); + draggedCategory = category.id; + this.updateContainer("dmCategories"); + let dragPreview = this.createDragPreview(node, event2); + document.removeEventListener("mousemove", mousemove); + document.removeEventListener("mouseup", mouseup); + let dragging = event3 => { + this.updateDragPreview(dragPreview, event3); + let placeholder = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmsdmchannelplaceholder, event3.target); + let categoryNode = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmspinnedchannelsheadercontainer, placeholder ? placeholder.previousSibling : event3.target); + let maybeHoveredCategory = categoryNode && categoryNode.getAttribute("categoryId"); + let update = maybeHoveredCategory != hoveredCategory; + if (maybeHoveredCategory) hoveredCategory = maybeHoveredCategory; + else hoveredCategory = null; + if (update) this.updateContainer("dmCategories"); + }; + let releasing = event3 => { + BDFDB.DOMUtils.remove(dragPreview); + if (hoveredCategory) releasedCategory = hoveredCategory; + else draggedCategory = null; + hoveredCategory = null; + this.updateContainer("dmCategories"); + document.removeEventListener("mousemove", dragging); + document.removeEventListener("mouseup", releasing); + }; + document.addEventListener("mousemove", dragging); + document.addEventListener("mouseup", releasing); + } + }; + let mouseup = _ => { + document.removeEventListener("mousemove", mousemove); + document.removeEventListener("mouseup", mouseup); + }; + document.addEventListener("mousemove", mousemove); + document.addEventListener("mouseup", mouseup); + }, + onClick: _ => { + if (foundDMs.length || !category.collapsed) { + category.collapsed = !category.collapsed; + BDFDB.DataUtils.save(category, this, "dmCategories", category.id); + this.updateContainer("dmCategories"); + } + }, + onContextMenu: event => { + BDFDB.ContextMenuUtils.open(this, event, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, { + children: [ + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: BDFDB.LanguageUtils.LanguageStrings.CATEGORY_SETTINGS, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "category-settings"), + action: event2 => { + BDFDB.ContextMenuUtils.close(BDFDB.DOMUtils.getParent(BDFDB.dotCN.contextmenu, event2.target)); + this.openCategorySettingsModal(category, "dmCategories"); + } + }), + BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: BDFDB.LanguageUtils.LanguageStrings.DELETE_CATEGORY, + id: BDFDB.ContextMenuUtils.createItemId(this.name, "remove-category"), + color: BDFDB.LibraryComponents.MenuItems.Colors.DANGER, + action: event2 => { + BDFDB.ContextMenuUtils.close(BDFDB.DOMUtils.getParent(BDFDB.dotCN.contextmenu, event2.target)); + BDFDB.DataUtils.remove(this, "dmCategories", category.id); + this.updateContainer("dmCategories"); + } + }) + ] + })); + }, + children: [ + BDFDB.ObjectUtils.is(color) ? BDFDB.ReactUtils.createElement("span", { + className: BDFDB.disCN.dmchannelheadertext, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextGradientElement, { + gradient: BDFDB.ColorUtils.createGradient(color), + children: category.name + }) + }) : BDFDB.ReactUtils.createElement("span", { + className: BDFDB.disCN.dmchannelheadertext, + style: {color: color}, + children: category.name, + }), + unreadAmount ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.NumberBadge, { + className: BDFDB.disCN._pindmspinnedchannelsheaderamount, + count: unreadAmount, + style: {backgroundColor: BDFDB.DiscordConstants.Colors.STATUS_RED} + }) : null, + settings.showCategoryAmount ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.NumberBadge, { + className: BDFDB.disCN._pindmspinnedchannelsheaderamount, + count: foundDMs.length, + style: {backgroundColor: BDFDB.DiscordConstants.Colors.BRAND} + }) : null, + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS._pindmspinnedchannelsheaderarrow + BDFDB.disCNS.channelheadericonwrapper + BDFDB.disCN.channelheadericonclickable, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCNS._pindmspinnedchannelsheaderarrow + BDFDB.disCN.channelheadericon, + nativeClass: true, + iconSVG: `` + }) + }) + ].filter(n => n) + }), + hoveredChannel == "header_" + category.id && BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.dmchannel + BDFDB.disCNS._pindmsdmchannelpinned + BDFDB.disCNS._pindmsdmchannelplaceholder + BDFDB.disCN.namecontainernamecontainer, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.namecontainerlayout + }) + }) + ].filter(n => n); + } + else e2.returnValue = null; + } + }}, {force: true, noCache: true}); + + let pinnedIds = BDFDB.ObjectUtils.toArray(e.instance.props.pinnedChannelIds).reverse(); + BDFDB.ModuleUtils.unpatch(this, e.instance, "renderDM"); + BDFDB.ModuleUtils.patch(this, e.instance, "renderDM", {before: e2 => { + if (e2.methodArguments[0] != 0) e2.methodArguments[1] += pinnedIds.slice(0, e2.methodArguments[0] - 1).flat().length; + }, after: e2 => { + if (e2.methodArguments[0] != 0) { + let id = e.instance.props.privateChannelIds[e2.methodArguments[1]]; + e2.returnValue = e.instance.props.channels[id] ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.PrivateChannelItems[e.instance.props.channels[id].isMultiUserDM() ? "GroupDM" : "DirectMessage"], Object.assign({ + key: id, + channel: e.instance.props.channels[id], + selected: e.instance.props.selectedChannelId == id + }, (e.instance.props.navigator || e.instance.props.listNavigator).getItemProps({ + index: e2.methodArguments[2] + }))) : null; + + let category = categories[e2.methodArguments[0] - 1]; + if (category) { + if (!id || (category.collapsed && e.instance.props.selectedChannelId != id) || !category.dms.includes(id) || draggedCategory == category.id || draggedChannel == id) e2.returnValue = null; + else if (hoveredCategory == category.id && [].concat(category.dms).reverse()[0] == id) e2.returnValue = [ + e2.returnValue, + BDFDB.ReactUtils.createElement("h2", { + className: BDFDB.disCNS.dmchannelheadercontainer + BDFDB.disCNS._pindmspinnedchannelsheadercontainer + BDFDB.disCNS._pindmsdmchannelplaceholder + BDFDB.disCN.namecontainernamecontainer + }) + ].filter(n => n); + else if (hoveredChannel == id) e2.returnValue = [ + e2.returnValue, + BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.dmchannel + BDFDB.disCNS._pindmsdmchannelpinned + BDFDB.disCNS._pindmsdmchannelplaceholder + BDFDB.disCN.namecontainernamecontainer, + children: BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.namecontainerlayout + }) + }) + ].filter(n => n); + } + } + }}, {force: true, noCache: true}); + } + } + + processUnreadDMs (e) { + e.instance.props.pinnedPrivateChannelIds = []; + let sortedRecents = this.sortAndUpdate("pinnedRecents"); + if (sortedRecents.length) { + e.instance.props.unreadPrivateChannelIds = []; + for (let pos in sortedRecents) { + let id = sortedRecents[pos]; + if (e.instance.props.channels[id]) { + if (!e.instance.props.pinnedPrivateChannelIds.includes(id)) e.instance.props.pinnedPrivateChannelIds.push(id); + if (!e.instance.props.unreadPrivateChannelIds.includes(id)) e.instance.props.unreadPrivateChannelIds.push(id); + } + } + e.instance.props.unreadPrivateChannelIds = e.instance.props.unreadPrivateChannelIds.concat(BDFDB.LibraryModules.DirectMessageUnreadStore.getUnreadPrivateChannelIds()); + if (e.returnvalue) { + if (draggedChannel && releasedChannel) { + let pinnedPrivateChannelIds = [].concat(e.instance.props.pinnedPrivateChannelIds), newData = {}; + BDFDB.ArrayUtils.remove(pinnedPrivateChannelIds, draggedChannel, true); + pinnedPrivateChannelIds.splice(pinnedPrivateChannelIds.indexOf(releasedChannel) + 1, 0, draggedChannel); + for (let pos in pinnedPrivateChannelIds) newData[pinnedPrivateChannelIds[pos]] = parseInt(pos); + BDFDB.DataUtils.save(newData, this, "pinnedRecents"); + draggedChannel = null; + releasedChannel = null; + BDFDB.ReactUtils.forceUpdate(e.instance); + } + if (draggedChannel) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {filter: child => BDFDB.ReactUtils.getValue(child, "props.channel.id") == draggedChannel}); + children.splice(index, 1); + } + if (this.hoveredChannel) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {filter: child => BDFDB.ReactUtils.getValue(child, "props.channel.id") == this.hoveredChannel}); + children.splice(index + 1, 0, BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.guildouter + BDFDB.disCN._pindmsrecentplaceholder, + children: BDFDB.ReactUtils.createElement("div", { + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.GuildComponents.Items.DragPlaceholder, {}) + }) + })); + } + } + } + else e.instance.props.unreadPrivateChannelIds = BDFDB.LibraryModules.DirectMessageUnreadStore.getUnreadPrivateChannelIds(); + } + + processPrivateChannel (e) { + if (e.instance.props.channel) { + let category = this.getCategory(e.instance.props.channel.id, "dmCategories"); + if (category) { + if (e.node) { + BDFDB.DOMUtils.addClass(e.node, BDFDB.disCN._pindmsdmchannelpinned); + e.node.removeEventListener("mousedown", e.node.PinDMsMouseDownListener); + if (!BDFDB.DataUtils.get(this, "settings", "sortInRecentOrder")) { + e.node.setAttribute("draggable", false); + e.node.PinDMsMouseDownListener = event => { + if (!BDFDB.BDUtils.isPluginEnabled("PinDMs")) e.node.removeEventListener("mousedown", e.node.PinDMsMouseDownListener); + else { + event = event.nativeEvent || event; + let mousemove = event2 => { + if (Math.sqrt((event.pageX - event2.pageX)**2) > 20 || Math.sqrt((event.pageY - event2.pageY)**2) > 20) { + BDFDB.ListenerUtils.stopEvent(event); + draggedChannel = e.instance.props.channel.id; + this.updateContainer("dmCategories"); + let dragPreview = this.createDragPreview(e.node, event2); + document.removeEventListener("mousemove", mousemove); + document.removeEventListener("mouseup", mouseup); + let dragging = event3 => { + this.updateDragPreview(dragPreview, event3); + let maybeHoveredChannel = null; + let categoryNode = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmspinnedchannelsheadercontainer, event3.target); + if (categoryNode) { + let hoveredCategoryId = categoryNode.getAttribute("categoryid"); + if (hoveredCategoryId && hoveredCategoryId == category.id) maybeHoveredChannel = "header_" + category.id; + } + else { + let placeholder = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmsdmchannelplaceholder, event3.target); + maybeHoveredChannel = (BDFDB.ReactUtils.findValue(BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmsdmchannelpinned, placeholder ? placeholder.previousSibling : event3.target), "channel", {up: true}) || {}).id; + let maybeHoveredCategory = maybeHoveredChannel && this.getCategory(maybeHoveredChannel, "dmCategories"); + if (!maybeHoveredCategory || maybeHoveredCategory.id != category.id) maybeHoveredChannel = null; + }; + let update = maybeHoveredChannel != hoveredChannel; + if (maybeHoveredChannel) hoveredChannel = maybeHoveredChannel; + else hoveredChannel = null; + if (update) this.updateContainer("dmCategories"); + }; + let releasing = event3 => { + BDFDB.DOMUtils.remove(dragPreview); + if (hoveredChannel) releasedChannel = hoveredChannel; + else draggedChannel = null; + hoveredChannel = null; + this.updateContainer("dmCategories"); + document.removeEventListener("mousemove", dragging); + document.removeEventListener("mouseup", releasing); + }; + document.addEventListener("mousemove", dragging); + document.addEventListener("mouseup", releasing); + } + }; + let mouseup = _ => { + document.removeEventListener("mousemove", mousemove); + document.removeEventListener("mouseup", mouseup); + }; + document.addEventListener("mousemove", mousemove); + document.addEventListener("mouseup", mouseup); + } + }; + e.node.addEventListener("mousedown", e.node.PinDMsMouseDownListener); + } + } + if (e.returnvalue) e.returnvalue.props.children = [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: BDFDB.LanguageUtils.LanguageStrings.UNPIN, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + className: BDFDB.disCN._pindmsunpinbutton, + onClick: event => { + BDFDB.ListenerUtils.stopEvent(event); + this.removeFromCategory(e.instance.props.channel.id, category, "dmCategories"); + }, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCN._pindmsunpinicon, + name: BDFDB.LibraryComponents.SvgIcon.Names.PIN + }) + }) + }), + e.returnvalue.props.children + ].flat(10).filter(n => n); + } + } + } + + processDirectMessage (e) { + if (e.instance.props.channel) { + if (e.node && e.methodname == "componentDidMount") { + BDFDB.DOMUtils.removeClass(e.node, BDFDB.disCN._pindmsrecentpinned); + e.node.removeEventListener("contextmenu", e.node.PinDMsContextMenuListener); + e.node.PinDMsContextMenuListener = event => {BDFDB.DMUtils.openMenu(e.instance.props.channel.id, event);}; + e.node.addEventListener("contextmenu", e.node.PinDMsContextMenuListener); + if (this.isPinned(e.instance.props.channel.id, "pinnedRecents")) { + BDFDB.DOMUtils.addClass(e.node, BDFDB.disCN._pindmsrecentpinned); + e.node.removeEventListener("mousedown", e.node.PinDMsMouseDownListener); + if (!BDFDB.DataUtils.get(this, "settings", "sortInRecentOrderGuild")) { + for (let child of e.node.querySelectorAll("a")) child.setAttribute("draggable", false); + e.node.PinDMsMouseDownListener = event => { + let mousemove = event2 => { + if (Math.sqrt((event.pageX - event2.pageX)**2) > 20 || Math.sqrt((event.pageY - event2.pageY)**2) > 20) { + BDFDB.ListenerUtils.stopEvent(event); + draggedChannel = e.instance.props.channel.id; + BDFDB.ModuleUtils.forceAllUpdates(this, "UnreadDMs"); + let dragPreview = this.createDragPreview(e.node, event2); + document.removeEventListener("mousemove", mousemove); + document.removeEventListener("mouseup", mouseup); + let dragging = event3 => { + this.updateDragPreview(dragPreview, event3); + let placeholder = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmsrecentplaceholder, event3.target); + let maybeHoveredChannel = (BDFDB.ReactUtils.findValue(BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmsrecentpinned, placeholder ? placeholder.previousSibling : event3.target), "channel", {up: true}) || {}).id; + let update = maybeHoveredChannel != hoveredChannel; + if (maybeHoveredChannel) hoveredChannel = maybeHoveredChannel; + else hoveredChannel = null; + if (update) BDFDB.ModuleUtils.forceAllUpdates(this, "UnreadDMs"); + }; + let releasing = event3 => { + BDFDB.DOMUtils.remove(dragPreview); + if (hoveredChannel) releasedChannel = hoveredChannel; + else draggedChannel = null; + hoveredChannel = null; + BDFDB.ModuleUtils.forceAllUpdates(this, "UnreadDMs"); + document.removeEventListener("mousemove", dragging); + document.removeEventListener("mouseup", releasing); + }; + document.addEventListener("mousemove", dragging); + document.addEventListener("mouseup", releasing); + } + }; + let mouseup = _ => { + document.removeEventListener("mousemove", mousemove); + document.removeEventListener("mouseup", mouseup); + }; + document.addEventListener("mousemove", mousemove); + document.addEventListener("mouseup", mouseup); + }; + e.node.addEventListener("mousedown", e.node.PinDMsMouseDownListener); + } + } + } + if (e.node && e.methodname == "componentWillUnmount") { + BDFDB.ModuleUtils.forceAllUpdates(this, "PrivateChannelsList"); + } + if (e.returnvalue && this.isPinned(e.instance.props.channel.id, "pinnedRecents") && BDFDB.DataUtils.get(this, "settings", "showPinIcon")) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {name:"BlobMask"}); + if (index > -1) children[index].props.upperLeftBadge = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.IconBadge, { + className: BDFDB.disCN.guildiconbadge, + disableColor: true, + style: {transform: "scale(-1, 1)"}, + icon: BDFDB.LibraryComponents.SvgIcon.Names.NOVA_PIN + }); + } + } + } + + generateID (type) { + if (!type) return null; + let categories = BDFDB.DataUtils.load(this, type); + let id = Math.round(Math.random() * 10000000000000000); + return categories[id] ? this.generateID() : id; + } + + filterDMs (dms) { + return dms.filter(id => BDFDB.LibraryModules.ChannelStore.getChannel(id)); + } + + addToCategory (id, category, type) { + if (!id || !category || !type) return; + let wasEmpty = !this.filterDMs(category.dms).length; + if (!category.dms.includes(id)) category.dms.unshift(id); + if (wasEmpty && category.dms.length) category.collapsed = false; + BDFDB.DataUtils.save(category, this, type, category.id); + this.updateContainer(type); + } + + removeFromCategory (id, category, type) { + if (!id || !category || !type) return; + BDFDB.ArrayUtils.remove(category.dms, id, true); + if (!this.filterDMs(category.dms).length) category.collapsed = true; + BDFDB.DataUtils.save(category, this, type, category.id); + this.updateContainer(type); + } + + getCategory (id, type) { + if (!id || !type) return null; + let categories = BDFDB.DataUtils.load(this, type); + for (let catId in categories) if (categories[catId].dms.includes(id)) return categories[catId]; + return null; + } + + sortAndUpdateCategories (type, reverse) { + let data = BDFDB.ObjectUtils.sort(BDFDB.DataUtils.load(this, type), "pos"), newData = {}; + let sorted = [], pos = 0, sort = id => { + if (sorted[pos] === undefined) { + newData[id] = Object.assign({}, data[id], {pos}); + sorted[pos] = newData[id]; + } + else { + pos++; + sort(id); + } + }; + for (let id in data) sort(id); + if (!BDFDB.equals(data, newData)) BDFDB.DataUtils.save(newData, this, type); + return (reverse ? sorted.reverse() : sorted).filter(n => n); + } + + sortDMsByTime (dms, type) { + if (dms.length > 1 && BDFDB.DataUtils.get(this, "settings", type == "dmCategories" ? "sortInRecentOrder" : "sortInRecentOrderGuild")) { + let timestamps = BDFDB.LibraryModules.DirectMessageStore.getPrivateChannelTimestamps(); + return [].concat(dms).sort(function (x, y) {return timestamps[x] > timestamps[y] ? -1 : timestamps[x] < timestamps[y] ? 1 : 0;}); + } + else return dms; + } + + openCategorySettingsModal (data, type, isNew) { + if (BDFDB.ObjectUtils.is(data) && type) BDFDB.ModalUtils.open(this, { + size: "MEDIUM", + header: BDFDB.LanguageUtils.LanguageStrings.CATEGORY_SETTINGS, + subheader: data.name, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: BDFDB.LanguageUtils.LanguageStrings.CATEGORY_NAME, + className: BDFDB.disCN.marginbottom20 + " input-categoryname", + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { + value: data.name, + placeholder: data.name, + autoFocus: true + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, { + className: BDFDB.disCN.dividerdefault + }) + ] + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { + title: this.labels.modal_colorpicker1_text, + className: BDFDB.disCN.marginbottom20, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ColorSwatches, { + color: data.color, + number: 1 + }) + ] + }) + ], + buttons: [{ + contents: isNew ? BDFDB.LanguageUtils.LanguageStrings.CREATE : BDFDB.LanguageUtils.LanguageStrings.SAVE, + color: "BRAND", + close: true, + click: modal => { + data.name = modal.querySelector(".input-categoryname " + BDFDB.dotCN.input).value.trim() || data.name; + + data.color = BDFDB.ColorUtils.getSwatchColor(modal, 1); + if (data.color != null && !BDFDB.ObjectUtils.is(data.color)) { + if (data.color[0] < 30 && data.color[1] < 30 && data.color[2] < 30) data.color = BDFDB.ColorUtils.change(data.color, 30); + else if (data.color[0] > 225 && data.color[1] > 225 && data.color[2] > 225) data.color = BDFDB.ColorUtils.change(data.color, -30); + } + + BDFDB.DataUtils.save(data, this, type, data.id); + + this.updateContainer(type); + } + }] + }); + } + + addPin (newid, type) { + if (!newid) return; + let pinnedDMs = BDFDB.DataUtils.load(this, type); + for (let id in pinnedDMs) pinnedDMs[id] = pinnedDMs[id] + 1; + pinnedDMs[newid] = 0; + BDFDB.DataUtils.save(pinnedDMs, this, type); + this.updateContainer(type); + } + + removePin (id, type) { + if (!id) return; + BDFDB.DataUtils.remove(this, type, id); + this.updateContainer(type); + } + + isPinned (id, type) { + return BDFDB.DataUtils.load(this, type, id) != undefined; + } + + updateContainer (type) { + switch (type) { + case "dmCategories": + BDFDB.ModuleUtils.forceAllUpdates(this, "PrivateChannelsList"); + break; + case "pinnedRecents": + BDFDB.ModuleUtils.forceAllUpdates(this, "UnreadDMs"); + break; + } + } + + sortAndUpdate (type) { + let data = BDFDB.DataUtils.load(this, type), newData = {}; + delete data[""]; + delete data["null"]; + let sortedDMs = [], existingDMs = [], sortDM = (id, pos) => { + if (sortedDMs[pos] === undefined) sortedDMs[pos] = id; + else sortDM(id, pos + 1); + }; + for (let id in data) sortDM(id, data[id]); + sortedDMs = sortedDMs.filter(n => n); + for (let pos in sortedDMs) { + newData[sortedDMs[pos]] = parseInt(pos); + if (BDFDB.LibraryModules.ChannelStore.getChannel(sortedDMs[pos])) existingDMs.push(sortedDMs[pos]); + } + if (!BDFDB.equals(data, newData)) BDFDB.DataUtils.save(newData, this, type); + return this.sortDMsByTime(existingDMs, type); + } + + forceUpdateAll (stopped) { + BDFDB.ReactUtils.forceUpdate(BDFDB.ReactUtils.findOwner(document.querySelector(BDFDB.dotCN.app), {name:"FluxContainer(PrivateChannels)", all:true, unlimited:true})); + BDFDB.ModuleUtils.forceAllUpdates(this); + } + + createDragPreview (div, event) { + if (!Node.prototype.isPrototypeOf(div)) return; + let dragPreview = div.cloneNode(true); + BDFDB.DOMUtils.addClass(dragPreview, BDFDB.disCN._pindmsdragpreview); + BDFDB.DOMUtils.remove(dragPreview.querySelector(BDFDB.dotCNC.guildlowerbadge + BDFDB.dotCNC.guildupperbadge + BDFDB.dotCN.guildpillwrapper)); + document.querySelector(BDFDB.dotCN.appmount).appendChild(dragPreview); + let rects = BDFDB.DOMUtils.getRects(dragPreview); + BDFDB.DOMUtils.hide(dragPreview); + dragPreview.style.setProperty("pointer-events", "none", "important"); + dragPreview.style.setProperty("left", event.clientX - (rects.width/2) + "px", "important"); + dragPreview.style.setProperty("top", event.clientY - (rects.height/2) + "px", "important"); + return dragPreview; + } + + updateDragPreview (dragPreview, event) { + if (!Node.prototype.isPrototypeOf(dragPreview)) return; + BDFDB.DOMUtils.show(dragPreview); + let rects = BDFDB.DOMUtils.getRects(dragPreview); + dragPreview.style.setProperty("left", event.clientX - (rects.width/2) + "px", "important"); + dragPreview.style.setProperty("top", event.clientY - (rects.height/2) + "px", "important"); + } + + setLabelsByLanguage () { + switch (BDFDB.LanguageUtils.getLanguage().id) { + case "hr": //croatian + return { + context_pindm_text: "Prikljucite Izravnu Poruku", + context_pinchannel_text: "Priložite popisu kanala", + context_unpinchannel_text: "Ukloni s popisa kanala", + context_addtonewcategory_text: "Dodavanje u novu kategoriju", + context_pinguild_text: "Priložite popisu poslužitelja", + context_unpinguild_text: "Ukloni s popisa poslužitelja", + header_pinneddms_text: "Prikvačene Izravne Poruke", + modal_colorpicker1_text: "Boja kategorije" + }; + case "da": //danish + return { + context_pindm_text: "Fastgør PB", + context_pinchannel_text: "Vedhæft til kanalliste", + context_unpinchannel_text: "Fjern fra kanalliste", + context_addtonewcategory_text: "Føj til ny kategori", + context_pinguild_text: "Vedhæft til serverliste", + context_unpinguild_text: "Fjern fra serverliste", + header_pinneddms_text: "Pinned Privat Beskeder", + modal_colorpicker1_text: "Kategori farve" + }; + case "de": //german + return { + context_pindm_text: "Direktnachricht anheften", + context_pinchannel_text: "An Kanalliste anheften", + context_unpinchannel_text: "Von Kanalliste loslösen", + context_addtonewcategory_text: "Zur neuen Kategorie hinzufügen", + context_pinguild_text: "An Serverliste anheften", + context_unpinguild_text: "Von Serverliste loslösen", + header_pinneddms_text: "Gepinnte Direktnachrichten", + modal_colorpicker1_text: "Kategoriefarbe" + }; + case "es": //spanish + return { + context_pindm_text: "Anclar MD", + context_pinchannel_text: "Adjuntar a la lista de canales", + context_unpinchannel_text: "Deshazte de la lista de canales", + context_addtonewcategory_text: "Agregar a nueva categoría", + context_pinguild_text: "Adjuntar a la lista de servidores", + context_unpinguild_text: "Deshazte de la lista de servidores", + header_pinneddms_text: "Mensajes Directos Fijados", + modal_colorpicker1_text: "Color de la categoría" + }; + case "fr": //french + return { + context_pindm_text: "Épingler MP", + context_pinchannel_text: "Épingler à la liste des salons", + context_unpinchannel_text: "Détacher de la liste des salons", + context_addtonewcategory_text: "Ajouter à une nouvelle catégorie", + context_pinguild_text: "Épingler à la liste de serveurs", + context_unpinguild_text: "Détacher de la liste de serveurs", + header_pinneddms_text: "Messages Prives Épinglés", + modal_colorpicker1_text: "Couleur de la catégorie" + }; + case "it": //italian + return { + context_pindm_text: "Fissa il messaggio diretto", + context_pinchannel_text: "Allega alla lista dei canali", + context_unpinchannel_text: "Rimuovi dalla lista dei canali", + context_addtonewcategory_text: "Aggiungi a nuova categoria", + context_pinguild_text: "Allega alla lista dei server", + context_unpinguild_text: "Rimuovi dalla lista dei server", + header_pinneddms_text: "Messaggi Diretti Aggiunti", + modal_colorpicker1_text: "Colore della categoria" + }; + case "nl": //dutch + return { + context_pindm_text: "PB pinnen", + context_pinchannel_text: "Pin naar de kanalenlijst", + context_unpinchannel_text: "Losmaken van kanalenlijst", + context_addtonewcategory_text: "Toevoegen aan nieuwe categorie", + context_pinguild_text: "Pin naar de serverlijst", + context_unpinguild_text: "Losmaken van serverlijst", + header_pinneddms_text: "Vastgezette Persoonluke Berichten", + modal_colorpicker1_text: "Categorie kleur" + }; + case "no": //norwegian + return { + context_pindm_text: "Fest DM", + context_pinchannel_text: "Fest på kanalliste", + context_unpinchannel_text: "Fjern fra kanalliste", + context_addtonewcategory_text: "Legg til i ny kategori", + context_pinguild_text: "Fest på serverliste", + context_unpinguild_text: "Fjern fra serverlisten", + header_pinneddms_text: "Pinned Direktemeldinger", + modal_colorpicker1_text: "Kategorifarge" + }; + case "pl": //polish + return { + context_pindm_text: "Przypnij PW", + context_pinchannel_text: "Dołącz do listy kanałów", + context_unpinchannel_text: "Usuń z listy kanałów", + context_addtonewcategory_text: "Dodaj do nowej kategorii", + context_pinguild_text: "Dołącz do listy serwerów", + context_unpinguild_text: "Usuń z listy serwerów", + header_pinneddms_text: "Prywatne Wiadomości Bezpośrednie", + modal_colorpicker1_text: "Kolor kategorii" + }; + case "pt-BR": //portuguese (brazil) + return { + context_pindm_text: "Fixar MD", + context_pinchannel_text: "Anexar à lista de canais", + context_unpinchannel_text: "Remover da lista de canais", + context_addtonewcategory_text: "Adicionar à nova categoria", + context_pinguild_text: "Anexar à lista de servidores", + context_unpinguild_text: "Remover da lista de servidores", + header_pinneddms_text: "Mensagens diretas fixadas", + modal_colorpicker1_text: "Cor da categoria" + }; + case "fi": //finnish + return { + context_pindm_text: "Kiinnitä yksityisviestit", + context_pinchannel_text: "Liitä kanavaluetteloon", + context_unpinchannel_text: "Poista kanavaluettelosta", + context_addtonewcategory_text: "Lisää uuteen luokkaan", + context_pinguild_text: "Liitä palvelinluetteloon", + context_unpinguild_text: "Poista palvelinluettelosta", + header_pinneddms_text: "Liitetyt yksityisviestit", + modal_colorpicker1_text: "Luokan väri" + }; + case "sv": //swedish + return { + context_pindm_text: "Fäst DM", + context_pinchannel_text: "Fäst till kanallista", + context_unpinchannel_text: "Ta bort från kanallistan", + context_addtonewcategory_text: "Lägg till i ny kategori", + context_pinguild_text: "Fäst till servernlista", + context_unpinguild_text: "Ta bort från servernlista", + header_pinneddms_text: "Inlagda Direktmeddelanden", + modal_colorpicker1_text: "Kategori färg" + }; + case "tr": //turkish + return { + context_pindm_text: "DM'yi Sabitle", + context_pinchannel_text: "Kanal listesine ekle", + context_unpinchannel_text: "Kanal listesinden kaldır", + context_addtonewcategory_text: "Yeni kategoriye ekle", + context_pinguild_text: "Sunucu listesine ekle", + context_unpinguild_text: "Sunucu listesinden kaldır", + header_pinneddms_text: "Direkt Mesajlar Sabitleyin", + modal_colorpicker1_text: "Kategori rengi" + }; + case "cs": //czech + return { + context_pindm_text: "Připnout PZ", + context_pinchannel_text: "Připojení k seznamu kanálů", + context_unpinchannel_text: "Odstranit ze seznamu kanálů", + context_addtonewcategory_text: "Přidat do nové kategorie", + context_pinguild_text: "Připojit ke seznamu serverů", + context_unpinguild_text: "Odstranit ze seznamu serverů", + header_pinneddms_text: "Připojené Přímá Zpráva", + modal_colorpicker1_text: "Barva kategorie" + }; + case "bg": //bulgarian + return { + context_pindm_text: "Закачени ДС", + context_pinchannel_text: "Прикачете към списъка с канали", + context_unpinchannel_text: "Премахване от списъка с канали", + context_addtonewcategory_text: "Добавяне към нова категория", + context_pinguild_text: "Прикачване към списъка със сървъри", + context_unpinguild_text: "Премахване от списъка със сървъри", + header_pinneddms_text: "Свързани директни съобщения", + modal_colorpicker1_text: "Цвят на категорията" + }; + case "ru": //russian + return { + context_pindm_text: "Закрепить ЛС", + context_pinchannel_text: "Прикрепить к списку каналов", + context_unpinchannel_text: "Удалить из списка каналов", + context_addtonewcategory_text: "Добавить в новую категорию", + context_pinguild_text: "Присоединить к списку серверов", + context_unpinguild_text: "Удалить из списка серверов", + header_pinneddms_text: "Прикрепленные Личные Сообщения", + modal_colorpicker1_text: "Цвет категории" + }; + case "uk": //ukrainian + return { + context_pindm_text: "Закріпити ОП", + context_pinchannel_text: "Додайте до списку каналів", + context_unpinchannel_text: "Видалити зі списку каналів", + context_addtonewcategory_text: "Додати до нової категорії", + context_pinguild_text: "Додайте до списку серверів", + context_unpinguild_text: "Видалити зі списку серверів", + header_pinneddms_text: "Прикріплені oсобисті повідомлення", + modal_colorpicker1_text: "Колір категорії" + }; + case "ja": //japanese + return { + context_pindm_text: "DMピン", + context_pinchannel_text: "チャンネルリストに添付", + context_unpinchannel_text: "チャンネルリストから削除", + context_addtonewcategory_text: "新しいカテゴリに追加", + context_pinguild_text: "サーバーリストに添付", + context_unpinguild_text: "サーバーリストから削除", + header_pinneddms_text: "固定された直接メッセージ", + modal_colorpicker1_text: "カテゴリーの色" + }; + case "zh-TW": //chinese (traditional) + return { + context_pindm_text: "引腳直接留言", + context_pinchannel_text: "附加到頻道列表", + context_unpinchannel_text: "從頻道列表中刪除", + context_addtonewcategory_text: "添加到新類別", + context_pinguild_text: "附加到服務器列表", + context_unpinguild_text: "從服務器列表中刪除", + header_pinneddms_text: "固定私人信息", + modal_colorpicker1_text: "類別顏色" + }; + case "ko": //korean + return { + context_pindm_text: "비공개 메시지 고정", + context_pinchannel_text: "채널 목록에 첨부", + context_unpinchannel_text: "채널 목록에서 삭제", + context_addtonewcategory_text: "새 카테고리에 추가", + context_pinguild_text: "서버 목록에 첨부", + context_unpinguild_text: "서버 목록에서 제거", + header_pinneddms_text: "고정 된 비공개 메시지", + modal_colorpicker1_text: "카테고리 색상" + }; + default: //default: english + return { + context_pindm_text: "Pin DM", + context_pinchannel_text: "Pin to Channellist", + context_unpinchannel_text: "Unpin from Channellist", + context_addtonewcategory_text: "Add to new Category", + context_pinguild_text: "Pin to Serverlist", + context_unpinguild_text: "Unpin from Serverlist", + header_pinneddms_text: "Pinned Direct Messages", + modal_colorpicker1_text: "Categorycolor" + }; + } + } + } +})(); \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/QuickMention.plugin.js b/.config/BetterDiscord/plugins/QuickMention.plugin.js new file mode 100644 index 0000000..64541e8 --- /dev/null +++ b/.config/BetterDiscord/plugins/QuickMention.plugin.js @@ -0,0 +1,78 @@ +//META{"name":"QuickMention","authorId":"278543574059057154","invite":"Jx3TjNS","donate":"https://www.paypal.me/MircoWittrien","patreon":"https://www.patreon.com/MircoWittrien","website":"https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/QuickMention","source":"https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/QuickMention/QuickMention.plugin.js"}*// + +var QuickMention = (_ => { + return class QuickMention { + getName () {return "QuickMention";} + + getVersion () {return "1.0.0";} + + getAuthor () {return "DevilBro";} + + getDescription () {return "Adds a mention entry to the message option toolbar.";} + + // Legacy + load () {} + + start () { + if (!window.BDFDB) window.BDFDB = {myPlugins:{}}; + if (window.BDFDB && window.BDFDB.myPlugins && typeof window.BDFDB.myPlugins == "object") window.BDFDB.myPlugins[this.getName()] = this; + let libraryScript = document.querySelector("head script#BDFDBLibraryScript"); + if (!libraryScript || (performance.now() - libraryScript.getAttribute("date")) > 600000) { + if (libraryScript) libraryScript.remove(); + libraryScript = document.createElement("script"); + libraryScript.setAttribute("id", "BDFDBLibraryScript"); + libraryScript.setAttribute("type", "text/javascript"); + libraryScript.setAttribute("src", "https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.min.js"); + libraryScript.setAttribute("date", performance.now()); + libraryScript.addEventListener("load", _ => {this.initialize();}); + document.head.appendChild(libraryScript); + } + else if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) this.initialize(); + this.startTimeout = setTimeout(_ => { + try {return this.initialize();} + catch (err) {console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not initiate plugin! " + err);} + }, 30000); + } + + initialize () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + if (this.started) return; + BDFDB.PluginUtils.init(this); + } + else { + console.error(`%c[${this.getName()}]%c`, 'color: #3a71c1; font-weight: 700;', '', 'Fatal Error: Could not load BD functions!'); + } + } + + stop () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + this.stopping = true; + + BDFDB.PluginUtils.clear(this); + } + } + + + // Begin of own functions + + onMessageOptionToolbar (e) { + if (e.instance.props.message.author.id != BDFDB.UserUtils.me.id && e.instance.props.message.type == BDFDB.DiscordConstants.MessageTypes.DEFAULT && BDFDB.UserUtils.can("SEND_MESSAGES")) e.returnvalue.props.children.unshift(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + key: "mention", + text: BDFDB.LanguageUtils.LanguageStrings.MENTION, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { + className: BDFDB.disCN.messagetoolbarbutton, + onClick: _ => { + BDFDB.LibraryModules.DispatchUtils.ComponentDispatch.dispatchToLastSubscribed(BDFDB.DiscordConstants.ComponentActions.INSERT_TEXT, { + content: `<@!${e.instance.props.message.author.id}>` + }); + }, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { + className: BDFDB.disCNS.messagetoolbaricon, + nativeClass: true, + name: BDFDB.LibraryComponents.SvgIcon.Names.NOVA_AT + }) + }) + })); + } + } +})(); \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/RemoveNicknames.config.json b/.config/BetterDiscord/plugins/RemoveNicknames.config.json new file mode 100644 index 0000000..1818ecb --- /dev/null +++ b/.config/BetterDiscord/plugins/RemoveNicknames.config.json @@ -0,0 +1,17 @@ +{ + "changelog": { + "currentversion": "1.3.0" + }, + "settings": { + "addNickname": false, + "changeInAutoComplete": true, + "changeInChatWindow": true, + "changeInMemberList": true, + "changeInMentions": true, + "changeInTyping": true, + "changeInVoiceChat": true, + "replaceBots": true, + "replaceOwn": false, + "swapPositions": false + } +} \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/RemoveNicknames.plugin.js b/.config/BetterDiscord/plugins/RemoveNicknames.plugin.js new file mode 100644 index 0000000..0390456 --- /dev/null +++ b/.config/BetterDiscord/plugins/RemoveNicknames.plugin.js @@ -0,0 +1,201 @@ +//META{"name":"RemoveNicknames","authorId":"278543574059057154","invite":"Jx3TjNS","donate":"https://www.paypal.me/MircoWittrien","patreon":"https://www.patreon.com/MircoWittrien","website":"https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/RemoveNicknames","source":"https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/RemoveNicknames/RemoveNicknames.plugin.js"}*// + +var RemoveNicknames = (_ => { + return class RemoveNicknames { + getName () {return "RemoveNicknames";} + + getVersion () {return "1.3.0";} + + getAuthor () {return "DevilBro";} + + getDescription () {return "Replace all nicknames with the actual accountnames.";} + + constructor () { + this.changelog = { + "fixed":[["Typing List","Works now"]] + }; + + this.patchedModules = { + before: { + AutocompleteUserResult: "render", + VoiceUser: "render", + MemberListItem: "render", + Message: "default", + MessageContent: "type", + }, + after: { + TypingUsers: "render" + } + }; + } + + initConstructor () { + this.defaults = { + settings: { + replaceOwn: {value:false, inner:false, description:"Replace your own name:"}, + replaceBots: {value:true, inner:false, description:"Replace the nickname of bots:"}, + addNickname: {value:false, inner:false, description:"Add nickname as parentheses:"}, + swapPositions: {value:false, inner:false, description:"Swap the position of username and nickname:"}, + changeInChatWindow: {value:true, inner:true, description:"Messages"}, + changeInMentions: {value:true, inner:true, description:"Mentions"}, + changeInVoiceChat: {value:true, inner:true, description:"Voice Channels"}, + changeInMemberList: {value:true, inner:true, description:"Member List"}, + changeInTyping: {value:true, inner:true, description:"Typing List"}, + changeInAutoComplete: {value:true, inner:true, description:"Autocomplete Menu"} + } + }; + } + + getSettingsPanel () { + if (!window.BDFDB || typeof BDFDB != "object" || !BDFDB.loaded || !this.started) return; + let settings = BDFDB.DataUtils.get(this, "settings"); + let settingsPanel, settingsItems = [], innerItems = []; + + for (let key in settings) (!this.defaults.settings[key].inner ? settingsItems : innerItems).push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { + className: BDFDB.disCN.marginbottom8, + type: "Switch", + plugin: this, + keys: ["settings", key], + label: this.defaults.settings[key].description, + value: settings[key] + })); + settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelInner, { + title: "Remove Nicknames in:", + first: settingsItems.length == 0, + last: true, + children: innerItems + })); + + return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, settingsItems); + } + + // Legacy + load () {} + + start () { + if (!window.BDFDB) window.BDFDB = {myPlugins:{}}; + if (window.BDFDB && window.BDFDB.myPlugins && typeof window.BDFDB.myPlugins == "object") window.BDFDB.myPlugins[this.getName()] = this; + let libraryScript = document.querySelector("head script#BDFDBLibraryScript"); + if (!libraryScript || (performance.now() - libraryScript.getAttribute("date")) > 600000) { + if (libraryScript) libraryScript.remove(); + libraryScript = document.createElement("script"); + libraryScript.setAttribute("id", "BDFDBLibraryScript"); + libraryScript.setAttribute("type", "text/javascript"); + libraryScript.setAttribute("src", "https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.min.js"); + libraryScript.setAttribute("date", performance.now()); + libraryScript.addEventListener("load", _ => {this.initialize();}); + document.head.appendChild(libraryScript); + } + else if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) this.initialize(); + this.startTimeout = setTimeout(_ => { + try {return this.initialize();} + catch (err) {console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not initiate plugin! " + err);} + }, 30000); + } + + initialize () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + if (this.started) return; + BDFDB.PluginUtils.init(this); + + this.forceUpdateAll(); + } + else console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not load BD functions!"); + } + + + stop () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + this.stopping = true; + + this.forceUpdateAll(); + + BDFDB.PluginUtils.clear(this); + } + } + + + // Begin of own functions + + onSettingsClosed (e) { + if (this.SettingsUpdated) { + delete this.SettingsUpdated; + BDFDB.ModuleUtils.forceAllUpdates(this); + } + } + + processAutocompleteUserResult (e) { + if (e.instance.props.user && e.instance.props.nick && BDFDB.DataUtils.get(this, "settings", "changeInAutoComplete")) { + let newName = this.getNewName(e.instance.props.user); + if (newName) e.instance.props.nick = newName; + } + } + + processVoiceUser (e) { + if (e.instance.props.user && e.instance.props.nick && BDFDB.DataUtils.get(this, "settings", "changeInVoiceChat")) { + let newName = this.getNewName(e.instance.props.user); + if (newName) e.instance.props.nick = newName; + } + } + + processMemberListItem (e) { + if (e.instance.props.user && e.instance.props.nick && BDFDB.DataUtils.get(this, "settings", "changeInMemberList")) { + let newName = this.getNewName(e.instance.props.user); + if (newName) e.instance.props.nick = newName; + } + } + + processTypingUsers (e) { + if (BDFDB.ObjectUtils.is(e.instance.props.typingUsers) && Object.keys(e.instance.props.typingUsers).length && BDFDB.DataUtils.get(this, "settings", "changeInTyping")) { + let users = Object.keys(e.instance.props.typingUsers).filter(id => id != BDFDB.UserUtils.me.id).filter(id => !BDFDB.LibraryModules.FriendUtils.isBlocked(id)).map(id => BDFDB.LibraryModules.UserStore.getUser(id)).filter(user => user); + if (users.length) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props: [["className", BDFDB.disCN.typingtext]]}); + if (index > -1 && BDFDB.ArrayUtils.is(children[index].props.children)) for (let child of children[index].props.children) if (child.type == "strong") { + let newName = this.getNewName(users.shift()); + if (newName) BDFDB.ReactUtils.setChild(child, newName); + } + } + } + } + + processMessage (e) { + let header = e.instance.props.childrenHeader; + if (header && header.props && header.props.message && header.props.message.nick) { + let newName = this.getNewName(header.props.message.author); + if (newName) header.props.message = new BDFDB.DiscordObjects.Message(Object.assign({}, header.props.message, {nick: newName})); + } + } + + processMessageContent (e) { + if (BDFDB.ArrayUtils.is(e.instance.props.content) && BDFDB.DataUtils.get(this, "settings", "changeInMentions")) for (let ele of e.instance.props.content) { + if (BDFDB.ReactUtils.isValidElement(ele) && ele.type && (ele.type.displayName || "").toLowerCase().indexOf("popout") > -1 && typeof ele.props.render == "function") { + if (BDFDB.ReactUtils.getValue(ele, "props.children.type.displayName") == "Mention") { + let newName = this.getNewName(BDFDB.LibraryModules.UserStore.getUser(ele.props.render().props.userId)); + if (newName) ele.props.children.props.children[0] = "@" + newName; + } + } + } + if (e.instance.props.message.type != BDFDB.DiscordConstants.MessageTypes.DEFAULT && e.instance.props.message.nick && BDFDB.DataUtils.get(this, "settings", "changeInChatWindow")) { + let newName = this.getNewName(e.instance.props.message.author); + if (newName) { + e.instance.props.message = new BDFDB.DiscordObjects.Message(Object.assign({}, e.instance.props.message, {nick: newName})); + e.instance.props.children.props.message = e.instance.props.message; + } + } + } + + getNewName (user, wrapper) { + if (!user) return null; + let settings = BDFDB.DataUtils.get(this, "settings"); + let member = BDFDB.LibraryModules.MemberStore.getMember(BDFDB.LibraryModules.LastGuildStore.getGuildId(), user.id) || {}; + if (!member.nick || user.id == BDFDB.UserUtils.me.id && !settings.replaceOwn || user.bot && !settings.replaceBots) return null; + let username = (BDFDB.BDUtils.isPluginEnabled("EditUsers") && BDFDB.DataUtils.load("EditUsers", "users", user.id) || {}).name || user.username; + return settings.addNickname ? (settings.swapPositions ? (member.nick + " (" + username + ")") : (username + " (" + member.nick + ")")) : username; + } + + forceUpdateAll () { + BDFDB.ModuleUtils.forceAllUpdates(this); + BDFDB.MessageUtils.rerenderAll(); + } + } +})(); \ No newline at end of file diff --git a/.config/BetterDiscord/plugins/SendLargeMessages.plugin.js b/.config/BetterDiscord/plugins/SendLargeMessages.plugin.js new file mode 100644 index 0000000..3827ee2 --- /dev/null +++ b/.config/BetterDiscord/plugins/SendLargeMessages.plugin.js @@ -0,0 +1,259 @@ +//META{"name":"SendLargeMessages","authorId":"278543574059057154","invite":"Jx3TjNS","donate":"https://www.paypal.me/MircoWittrien","patreon":"https://www.patreon.com/MircoWittrien","website":"https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/SendLargeMessages","source":"https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/SendLargeMessages/SendLargeMessages.plugin.js"}*// + +var SendLargeMessages = (_ => { + return class SendLargeMessages { + getName () {return "SendLargeMessages";} + + getVersion () {return "1.6.4";} + + getAuthor () {return "DevilBro";} + + getDescription () {return "Opens a popout when your message is too large, which allows you to automatically send the message in several smaller messages.";} + + constructor () { + this.patchedModules = { + before: { + ChannelTextAreaForm: "render", + ChannelEditorContainer: "render" + }, + after: { + ChannelTextAreaContainer: "render", + } + }; + } + + initConstructor () { + this.messageDelay = 1000; //changing at own risk, might result in bans or mutes + + this.css = ` + .${this.name}-modal textarea { + height: 50vh; + }`; + } + + // Legacy + load () {} + + start () { + if (!window.BDFDB) window.BDFDB = {myPlugins:{}}; + if (window.BDFDB && window.BDFDB.myPlugins && typeof window.BDFDB.myPlugins == "object") window.BDFDB.myPlugins[this.getName()] = this; + let libraryScript = document.querySelector("head script#BDFDBLibraryScript"); + if (!libraryScript || (performance.now() - libraryScript.getAttribute("date")) > 600000) { + if (libraryScript) libraryScript.remove(); + libraryScript = document.createElement("script"); + libraryScript.setAttribute("id", "BDFDBLibraryScript"); + libraryScript.setAttribute("type", "text/javascript"); + libraryScript.setAttribute("src", "https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.min.js"); + libraryScript.setAttribute("date", performance.now()); + libraryScript.addEventListener("load", _ => {this.initialize();}); + document.head.appendChild(libraryScript); + } + else if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) this.initialize(); + this.startTimeout = setTimeout(_ => { + try {return this.initialize();} + catch (err) {console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not initiate plugin! " + err);} + }, 30000); + } + + initialize () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + if (this.started) return; + BDFDB.PluginUtils.init(this); + + BDFDB.ModuleUtils.forceAllUpdates(this); + } + else console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not load BD functions!"); + } + + + stop () { + if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { + this.stopping = true; + + BDFDB.ModuleUtils.forceAllUpdates(this); + + BDFDB.PluginUtils.clear(this); + } + } + + + // Begin of own functions + + processChannelTextAreaForm (e) { + if (!BDFDB.ModuleUtils.isPatched(this, e.instance, "handleSendMessage")) BDFDB.ModuleUtils.patch(this, e.instance, "handleSendMessage", {instead: e2 => { + if (e2.methodArguments[0].length > BDFDB.DiscordConstants.MAX_MESSAGE_LENGTH) { + e2.stopOriginalMethodCall(); + let messages = this.formatText(e2.methodArguments[0]); + messages.filter(n => n).forEach((message, i) => { + BDFDB.TimeUtils.timeout(_ => { + e2.originalMethod(message); + if (i >= messages.length-1) BDFDB.NotificationUtils.toast(this.labels.toast_allsent_text, {type:"success"}); + }, this.messageDelay * i); + }); + return Promise.resolve({ + shouldClear: true, + shouldRefocus: true + }); + } + else return e2.callOriginalMethodAfterwards(); + }}, {force: true, noCache: true}); + } + + processChannelTextAreaContainer (e) { + if (e.returnvalue.ref && e.returnvalue.ref.current && BDFDB.DOMUtils.getParent(BDFDB.dotCN.chatform, e.returnvalue.ref.current)) { + let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {name: "SlateCharacterCount"}); + if (index > -1) { + let text = BDFDB.LibraryModules.SlateSelectionUtils.serialize(children[index].props.document, "raw"); + if (text.length > BDFDB.DiscordConstants.MAX_MESSAGE_LENGTH) children[index] = BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCNS.textareacharcounter + BDFDB.disCN.textareacharcountererror, + children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { + text: Math.ceil(text.length / BDFDB.DiscordConstants.MAX_MESSAGE_LENGTH * (39/40)) + " " + BDFDB.LanguageUtils.LanguageStrings.MESSAGES, + children: BDFDB.ReactUtils.createElement("span", { + children: BDFDB.DiscordConstants.MAX_MESSAGE_LENGTH - text.length + }) + }) + }); + } + } + } + + processChannelEditorContainer (e) { + if (e.instance.props.type && e.instance.props.type == BDFDB.DiscordConstants.TextareaTypes.NORMAL) e.instance.props.shouldUploadLongMessages = false; + } + + formatText (text) { + text = text.replace(/\t/g, " "); + let longwords = text.match(/[\S]{1800,}/gm); + if (longwords) for (let longword of longwords) { + let count1 = 0; + let shortwords = []; + longword.split("").forEach(c => { + if (shortwords[count1] && shortwords[count1].length >= BDFDB.DiscordConstants.MAX_MESSAGE_LENGTH * (19/20)) count1++; + shortwords[count1] = shortwords[count1] ? shortwords[count1] + c : c; + }); + text = text.replace(longword, shortwords.join(" ")); + } + let messages = []; + let count2 = 0; + text.split(" ").forEach((word) => { + if (messages[count2] && (messages[count2] + "" + word).length > BDFDB.DiscordConstants.MAX_MESSAGE_LENGTH * (39/40)) count2++; + messages[count2] = messages[count2] ? messages[count2] + " " + word : word; + }); + + let insertCodeBlock = null, insertCodeLine = null; + for (let j = 0; j < messages.length; j++) { + if (insertCodeBlock) { + messages[j] = insertCodeBlock + messages[j]; + insertCodeBlock = null; + } + else if (insertCodeLine) { + messages[j] = insertCodeLine + messages[j]; + insertCodeLine = null; + } + + let codeBlocks = messages[j].match(/`{3,}[\S]*\n|`{3,}/gm); + let codeLines = messages[j].match(/[^`]{0,1}`{1,2}[^`]|[^`]`{1,2}[^`]{0,1}/gm); + + if (codeBlocks && codeBlocks.length % 2 == 1) { + messages[j] = messages[j] + "```"; + insertCodeBlock = codeBlocks[codeBlocks.length-1] + "\n"; + } + else if (codeLines && codeLines.length % 2 == 1) { + insertCodeLine = codeLines[codeLines.length-1].replace(/[^`]/g, ""); + messages[j] = messages[j] + insertCodeLine; + } + } + + return messages; + } + + setLabelsByLanguage () { + switch (BDFDB.LanguageUtils.getLanguage().id) { + case "hr": //croatian + return { + toast_allsent_text: "Sve veliku poslane." + }; + case "da": //danish + return { + toast_allsent_text: "Alle beskeder sendes." + }; + case "de": //german + return { + toast_allsent_text: "Alle Nachrichten versendet." + }; + case "es": //spanish + return { + toast_allsent_text: "Todos los mensajes enviados." + }; + case "fr": //french + return { + toast_allsent_text: "Tous les messages envoyés" + }; + case "it": //italian + return { + toast_allsent_text: "Tutti i messaggi inviati." + }; + case "nl": //dutch + return { + toast_allsent_text: "Alle berichten verzonden." + }; + case "no": //norwegian + return { + toast_allsent_text: "Alle meldinger sendt." + }; + case "pl": //polish + return { + toast_allsent_text: "Wszystkie wiadomości zostały wysłane." + }; + case "pt-BR": //portuguese (brazil) + return { + toast_allsent_text: "Todas as mensagens enviadas." + }; + case "fi": //finnish + return { + toast_allsent_text: "Kaikki lähetetyt viestit." + }; + case "sv": //swedish + return { + toast_allsent_text: "Alla meddelanden skickade." + }; + case "tr": //turkish + return { + toast_allsent_text: "Tüm mesajlar gönderildi." + }; + case "cs": //czech + return { + toast_allsent_text: "Všechny zprávy byly odeslány." + }; + case "bg": //bulgarian + return { + toast_allsent_text: "Всички изпратени съобщения." + }; + case "ru": //russian + return { + toast_allsent_text: "Все отправленные сообщения." + }; + case "uk": //ukrainian + return { + toast_allsent_text: "Всі повідомлення надіслано." + }; + case "ja": //japanese + return { + toast_allsent_text: "すべてのメッセージが送信されました。" + }; + case "zh-TW": //chinese (traditional) + return { + toast_allsent_text: "發送的所有消息。" + }; + case "ko": //korean + return { + toast_allsent_text: "모든 메시지가 전송되었습니다." + }; + default: //default: english + return { + toast_allsent_text: "All messages sent." + }; + } + } + } +})(); \ No newline at end of file diff --git a/.config/BetterDiscord/themes/Material-Discord.theme.css b/.config/BetterDiscord/themes/Material-Discord.theme.css new file mode 100644 index 0000000..c3f0ced --- /dev/null +++ b/.config/BetterDiscord/themes/Material-Discord.theme.css @@ -0,0 +1,3 @@ +//META{"name":"Material_Discord","description":"A theme based on Google's Material Design","author":"CapnKitten","version":"2.1.4.1"}*//{} + +@import url(https://capnkitten.github.io/BetterDiscord/Material-Discord/css/source.css?v=2.1.4.1); diff --git a/.config/BetterDiscord/themes/MinimalCord.theme.css b/.config/BetterDiscord/themes/MinimalCord.theme.css new file mode 100644 index 0000000..87e66f9 --- /dev/null +++ b/.config/BetterDiscord/themes/MinimalCord.theme.css @@ -0,0 +1,138 @@ +/** + * @name MinimalCord + * @author Gibbu#1211 + * @version 1.0.0 + * @description Changes Discord enough to give it a fresh feel while also making it darker. Supports both Light and Dark themes. + * @source https://github.com/Gibbu/BetterDiscord-Themes/tree/master/MinimalCord + * @website https://www.gibbu.me +*/ + +@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,900'); +@import url("https://gibbu.github.io/BetterDiscord-Themes/MinimalCord/base.css"); + +/* Black Box user tags */ +@import url('https://monstrousdev.github.io/themes/addons/user-tags.css'); + +:root { + /* --accent: 233, 86, 120; DEFAULT: 22, 188, 249 */ + --chat-avatar-size: 48px; /* Changes size of chat avatars | DEFAULT: 40px */ + --search-results-width: 25vw; /* Width of server results. For a fixed width use px. | DEFAULT: 25vw */ + --emoji-picker-height: 260px; /* Height of Emoji picker | DEFAULT: 260px */ + --font: 'JetBrainsMono NF'; +} + +.theme-dark { + --foreground-1: var(--pywal-bg0) !important; + --foreground-2: var(--pywal-bg1) !important; + --foreground-divider: var(--pywal-color15) !important; + --background-primary: var(--pywal-bg3) !important; + --background-secondary: var(--pywal-bg2) !important; + --background-tertiary: var(--pywal-bg0) !important; + --background-floating: var(--pywal-bg0) !important; + --background-1: var(--pywal-bg0) !important; + --background-2: var(--pywal-serverside) !important; + --box-shadow: 0 3px 30px -5px #0008 !important; + --scrollbar: var(--pywal-bg1) !important; + --tooltips: var(--pywal-bg2) !important; + --pages-box: var(--message-box) !important; + --pages-box-footer: var(--pywal-bg0) !important; + --pages-separator: #ffffff0d !important; + --user-popout: var(--dropdown) !important; + --user-popout-body: var(--pywal-bg0) !important; + --user-popout-message: var(--pywal-color0) !important; + --settings-box: var(--message-box) !important; + --settings-box-hover: var(--pywal-color0) !important; + --settings-box-footer: var(--pywal-color0) !important; + --settings-input: var(--pywal-color0) !important; + --settings-paginator: #00000033 !important; + --settings-separator: #ffffff0d !important; + --dropdown: var(--pywal-bg1) !important; + --dropdown-header: var(--pywal-bg3) !important; + --dropdown-item-hover: var(--pywal-bg1) !important; + --message-box: #0000 !important; + --message-box-divider: var(--pywal-bg2) !important; + --message-embed: var(--pywal-bg2) !important; + --message-button: var(--pywal-bg3) !important; + --server-background: var(--pywal-serverside) !important; + --server-selected: var(--pywal-serverside) !important; + --search-filter-hover: var(--pywal-bg1) !important; + --search-result-header: var(--pywal-bg1) !important; + --channel-selected: var(--pywal-bg2) !important; + --channel-selected-bg: var(--channel-selected) !important; + --channel-hovered: var(--pywal-bg1) !important; + --channel-hovered-bg: var(--pywal-bg1) !important; + --modal-connection: var(--background-1) !important; + --modal-list: var(--pywal-color0) !important; + --modal-badge-invert: invert(0) !important; + --modal-bg1-search: var(--search-filter-hover) !important; + --text-1: #fff !important; + --text-2: #c9c9c9 !important; + --text-3: #999a9b !important; + --text-4: #717173 !important; + --text-dark: #565759 !important; +} + +.da-attachWrapper, .da-buttons { + width: 0; + overflow: hidden; +} + +.da-channelTextArea { + margin-top: 0 !important; + margin-bottom: 8px !important; +} + +.da-form { + margin-left: 0 !important; + margin-right: 0 !important; +} + +.da-username { + filter: saturate(0.4) brightness(2); +} + +.da-itemCard > div > div { + background: var(--background-secondary) !important; +} + +.da-channelTextArea > div { + box-shadow: none !important; + background: var(--foreground-1) !important; +} + +.da-form::before { + background: transparent !important; +} + +.da-nowPlayingColumn { + background: var(--background-1) !important; +} + +.da-videoControls { + margin: 0px !important; +} + +body { + background: var(--pywal-transparent50) !important; +} + +.da-privateChannelsHeaderContainer .da-numberBadge.da-base { + background-color: var(--pywal-color6) !important; + color: var(--pywal-readableOn-color6) !important; +} + +.da-circleIcon, +.da-circleIconButton { + color: var(--pywal-readable-color6) !important; +} + +section.da-container > .da-children > .da-tabBar > .da-item:not(.da-themed), +.bd-guild .da-item, +.da-tutorialContainer .da-pill .da-item { + background-color: var(--pywal-readable-color6) !important; + color: var(--pywal-readableOn-color6) !important; +} + +.da-actionButton { + background-color: transparent !important; +} diff --git a/.config/BetterDiscord/themes/glass.theme.css b/.config/BetterDiscord/themes/glass.theme.css new file mode 100644 index 0000000..a6dfb72 --- /dev/null +++ b/.config/BetterDiscord/themes/glass.theme.css @@ -0,0 +1,40 @@ +/** + * @name Glass + * @author Loekaars#8205 + * @version 1.0.0 + * @description Transparency required +*/ + +body #app-mount, +body #app-mount .da-app .da-app, +body #app-mount .da-app .da-app .da-layers .da-layer, +.da-scroller, +.da-bg, +.da-container > nav { + background: transparent !important; +} + +.da-sidebar .da-panels .da-container, +.da-sidebar .da-panels { + background: transparent !important; +} + +:root { + --server-selected: transparent !important; + --server-folders: var(--pywal-serverfolder-transparent) !important; + --server-background: transparent !important; +} + +.da-expandedFolderBackground { + border-radius: 16px !important; +} + +body, +.da-sidebar { + background: var(--pywal-serverfolder-transparent) !important; +} + +.da-folder { + background: transparent !important; +} + diff --git a/.config/BetterDiscord/themes/pywal.theme.css b/.config/BetterDiscord/themes/pywal.theme.css new file mode 100644 index 0000000..19b01e4 --- /dev/null +++ b/.config/BetterDiscord/themes/pywal.theme.css @@ -0,0 +1,75 @@ +/** + * @name pywal + * @author Loekaars#8205 + * @version 1.0.0 + * @description Cool beans +*/ + +:root { + --pywal-shade0: #18191C; + --pywal-shade1: #1D1E21; + --pywal-shade2: #1F2024; + --pywal-shade3: #222227; + --pywal-shade4: #1F202A; + + --accent: 102, 97, 90; + + --pywal-serverside: #121213; + --pywal-serverfolder-transparent: hsla(240, 2.4%, 7.2%, 0.55); + + --pywal-bg0: #141415; + --pywal-bg1: #181819; + --pywal-bg2: #1C1C1D; + --pywal-bg3: #202022; + + --pywal-color0: #141415; + --pywal-color1: #292b37; + --pywal-color2: #3c2a27; + --pywal-color3: #3b3033; + --pywal-color4: #3f3330; + --pywal-color5: #4b4240; + --pywal-color6: #66615a; + --pywal-color7: #c4c4c4; + --pywal-color8: #4e4e4f; + --pywal-color9: #292b37; + --pywal-color10: #3c2a27; + --pywal-color11: #3b3033; + --pywal-color12: #3f3330; + --pywal-color13: #4b4240; + --pywal-color14: #66615a; + --pywal-color15: #c4c4c4; + + --pywal-readable-color0: #6B6B70; + --pywal-readable-color1: #585B71; + --pywal-readable-color2: #745550; + --pywal-readable-color3: #67565A; + --pywal-readable-color4: #665450; + --pywal-readable-color5: #4B4240; + --pywal-readable-color6: #66615A; + --pywal-readable-color7: #C4C4C4; + --pywal-readable-color8: #4E4E4F; + --pywal-readable-color9: #585B71; + --pywal-readable-color10: #745550; + --pywal-readable-color11: #67565A; + --pywal-readable-color12: #665450; + --pywal-readable-color13: #4B4240; + --pywal-readable-color14: #66615A; + --pywal-readable-color15: #C4C4C4; + + --pywal-readableOn-color0: #c4c4c4; + --pywal-readableOn-color1: #c4c4c4; + --pywal-readableOn-color2: #c4c4c4; + --pywal-readableOn-color3: #c4c4c4; + --pywal-readableOn-color4: #c4c4c4; + --pywal-readableOn-color5: #c4c4c4; + --pywal-readableOn-color6: #c4c4c4; + --pywal-readableOn-color7: #141415; + --pywal-readableOn-color8: #c4c4c4; + --pywal-readableOn-color9: #c4c4c4; + --pywal-readableOn-color10: #c4c4c4; + --pywal-readableOn-color11: #c4c4c4; + --pywal-readableOn-color12: #c4c4c4; + --pywal-readableOn-color13: #c4c4c4; + --pywal-readableOn-color14: #c4c4c4; + --pywal-readableOn-color15: #141415; +} \ No newline at end of file diff --git a/.config/brave-flags.conf b/.config/brave-flags.conf new file mode 100644 index 0000000..cb077db --- /dev/null +++ b/.config/brave-flags.conf @@ -0,0 +1 @@ +--force-dark-mode --load-extension=/home/loek/.cache/wal/chromium diff --git a/.config/chromium-flags.conf b/.config/chromium-flags.conf new file mode 100644 index 0000000..6fc50e8 --- /dev/null +++ b/.config/chromium-flags.conf @@ -0,0 +1 @@ +--force-dark-mode --load-extension="/home/loek/.cache/wal/chromium/" diff --git a/.config/coc/commands b/.config/coc/commands new file mode 100644 index 0000000..d173b4d --- /dev/null +++ b/.config/coc/commands @@ -0,0 +1,3 @@ +clangd.install +snippets.editSnippets +workspace.showOutput \ No newline at end of file diff --git a/.config/coc/extensions/package.json b/.config/coc/extensions/package.json new file mode 100644 index 0000000..432cb8a --- /dev/null +++ b/.config/coc/extensions/package.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "coc-clangd": ">=0.4.10", + "coc-css": ">=1.2.3", + "coc-emmet": ">=1.1.4", + "coc-html": ">=1.2.4", + "coc-json": ">=1.2.6", + "coc-neosnippet": ">=1.2.2", + "coc-python": ">=1.2.12", + "coc-snippets": ">=2.1.28", + "coc-tsserver": ">=1.5.2", + "coc-vimtex": ">=1.0.3" + } +} \ No newline at end of file diff --git a/.config/coc/snippets-mru b/.config/coc/snippets-mru new file mode 100644 index 0000000..b58504a --- /dev/null +++ b/.config/coc/snippets-mru @@ -0,0 +1,9 @@ +newdocument +newhtml +for +try/except/else +try/except +\begin +gerrit +nopagenumbers +snippet \ No newline at end of file diff --git a/.config/coc/ultisnips/css.snippets b/.config/coc/ultisnips/css.snippets new file mode 100644 index 0000000..fc67c2e --- /dev/null +++ b/.config/coc/ultisnips/css.snippets @@ -0,0 +1,5 @@ +snippet resetFontWeight "reset font-weight for mobile browsers" +h1, h2, h3, h4, h5, h6 { + font-weight: normal !important; +} +endsnippet diff --git a/.config/coc/ultisnips/html.snippets b/.config/coc/ultisnips/html.snippets new file mode 100644 index 0000000..1730a7a --- /dev/null +++ b/.config/coc/ultisnips/html.snippets @@ -0,0 +1,16 @@ +snippet newhtml "HTML Starting Point" + + + + + + ${1:Page Title} + + + + + + ${0} + + +endsnippet diff --git a/.config/coc/ultisnips/tex.snippets b/.config/coc/ultisnips/tex.snippets new file mode 100644 index 0000000..056f562 --- /dev/null +++ b/.config/coc/ultisnips/tex.snippets @@ -0,0 +1,34 @@ +snippet newdocument "Starting point for a new LaTeX document" +\\documentclass[12pt, a4paper, hidelinks]{article} +\\setlength{\\marginparwidth}{2.54cm} + +% Packages +\\usepackage{fullpage} + +% Skip +\\bigskipamount=.7cm +\\medskipamount=.4cm +\\parindent=.3cm + +% Document +\\begin{document} + ${0} +\\end{document} +endsnippet + +snippet nonumberedchapters "Removes chapter numbers" +% Unnumber chapter headings +\\makeatletter +\\def\\@seccntformat#1{ + \\expandafter\\ifx\\csname c@#1\\endcsname\\c@section\\else + \\csname the#1\\endcsname\\quad + \\fi} +\\makeatother +endsnippet + +snippet beginend "begin/end snippet" +\\begin\{${1}\} +${0} +\\end\{${1}\} +endsnippet + diff --git a/.config/i3/config b/.config/i3/config new file mode 100644 index 0000000..7e6afb4 --- /dev/null +++ b/.config/i3/config @@ -0,0 +1,165 @@ +# variables +set $mod Mod4 +set $inner_gaps 10 +set $outer_gaps 0 + +# set gaps +gaps inner $inner_gaps +gaps outer $outer_gaps + +# border color +# pywal-start +client.focused #66615a #141415 #c4c4c4 #66615a #585653 +client.focused_inactive #5E5E5F #4e4e4f #c4c4c4 #181819 #141415 +client.unfocused #141415 #101011 #9D9D9D #141415 #101011 +client.urgent #141415 #e95678 #c4c4c4 #e95678 #e95678 +client.placeholder #000000 #0c0c0c #c4c4c4 #000000 #0c0c0c +client.background #c4c4c4 + +# pywal-end + +# font +font pango:Fira Code 9 + +# no idea what these do but they were in here by default +exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork +exec --no-startup-id nm-applet +set $refresh_i3status killall -SIGUSR1 i3status + +# Voulme keybinds +bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +5% && $refresh_i3status +bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5% && $refresh_i3status +bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle && $refresh_i3status + +# window interactions +floating_modifier $mod +bindsym $mod+q kill + +# Replace window decorations with a border +for_window [class=".*"] border pixel 1 + +# Keybinds +bindsym $mod+Escape exec "dm-tool lock" +bindsym $mod+space exec "rofi -show drun -show-icons" +bindsym $mod+Return exec konsole +bindsym $mod+p exec bwmenu +bindsym Print exec "flameshot gui" + +bindsym XF86AudioPlay exec "playerctl play-pause" +bindsym XF86AudioNext exec "playerctl next" +bindsym XF86AudioPrev exec "playerctl previous" + +# 60% keyboard media controls +bindsym Mod1+Mod4+k exec "mpc toggle" +bindsym Mod1+Mod4+l exec "mpc next" +bindsym Mod1+Mod4+j exec "mpc prev" +bindsym Mod1+Mod4+minus exec "pactl set-sink-volume @DEFAULT_SINK@ -5%" +bindsym Mod1+Mod4+equal exec "pactl set-sink-volume @DEFAULT_SINK@ +5%" + +# Gaps +bindsym $mod+i gaps inner current plus 5 +bindsym $mod+Shift+i gaps inner current minus 5 +bindsym $mod+o gaps outer current plus 5 +bindsym $mod+Shift+o gaps outer current minus 5 +bindsym $mod+n gaps inner current set -1; gaps outer current set 0 +bindsym $mod+d gaps inner current set $inner_gaps; gaps outer current set $outer_gaps; + +# Autostart +exec "polybar main -c ~/.config/polybar/config.ini &" +exec "node ~/.local/share/bin/pywal/wall.js &" +exec "exec picom --experimental-backends &" +exec "node ~/.local/share/bin/pester/index.js &" +exec "flameshot &" + +# change focus +bindsym $mod+h focus left +bindsym $mod+j focus down +bindsym $mod+k focus up +bindsym $mod+l focus right + +# move focused window +bindsym $mod+Shift+h move left +bindsym $mod+Shift+j move down +bindsym $mod+Shift+k move up +bindsym $mod+Shift+l move right + +# resize focused window +bindsym $mod+bracketright resize grow height 10 px +bindsym $mod+bracketleft resize shrink height 10 px +bindsym $mod+period resize grow width 10 px +bindsym $mod+comma resize shrink width 10 px + +# split in horizontal orientation +bindsym $mod+bar split h +bindsym $mod+minus split v + +# enter fullscreen mode for the focused container +bindsym $mod+f fullscreen toggle + +# toggle tiling / floating +bindsym $mod+Shift+space floating toggle + +# change focus between tiling / floating windows +bindsym $mod+Alt+space focus mode_toggle + +# focus the parent container +bindsym $mod+Shift+a focus parent + +# focus the child container +#bindsym $mod+d focus child + +# disable mouse teleports +mouse_warping none + +# workspaces +set $ws1 "1" +set $ws2 "2" +set $ws3 "3" +set $ws4 "4" +set $ws5 "5" +set $ws6 "6" +set $ws7 "7" +set $ws8 "8" + +# switch to workspace +bindsym $mod+1 workspace number $ws1 +bindsym $mod+2 workspace number $ws2 +bindsym $mod+3 workspace number $ws3 +bindsym $mod+4 workspace number $ws4 +bindsym $mod+5 workspace number $ws5 +bindsym $mod+6 workspace number $ws6 +bindsym $mod+7 workspace number $ws7 +bindsym $mod+8 workspace number $ws8 + +# move focused container to workspace +bindsym $mod+Shift+1 move container to workspace number $ws1 +bindsym $mod+Shift+2 move container to workspace number $ws2 +bindsym $mod+Shift+3 move container to workspace number $ws3 +bindsym $mod+Shift+4 move container to workspace number $ws4 +bindsym $mod+Shift+5 move container to workspace number $ws5 +bindsym $mod+Shift+6 move container to workspace number $ws6 +bindsym $mod+Shift+7 move container to workspace number $ws7 +bindsym $mod+Shift+8 move container to workspace number $ws8 + +# always floating windows +for_window [class="Steam"] floating enable +for_window [class="cinquo"] floating enable +for_window [title="Farge"] floating enable +for_window [window_type=notification] floating enable + +for_window [class="Unturned.x86_64"] floating disable +for_window [class="Unturned.x86_64"] fullscreen disable + +# reload the configuration file +bindsym $mod+Shift+c reload +# restart i3 inplace (preserves your layout/session, can be used to upgrade i3) +bindsym $mod+Shift+r restart +# exit i3 (logs you out of your X session) +# bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'" + +# bar { +# mode hide +# hidden_state hide +# modifier none +# tray_output none +# } diff --git a/.config/i3/config.backup b/.config/i3/config.backup new file mode 100644 index 0000000..4d3a098 --- /dev/null +++ b/.config/i3/config.backup @@ -0,0 +1,140 @@ +# variables +set $mod Mod4 +set $inner_gaps 10 +set $outer_gaps 0 +set $polybar_gap 30 + +# set gaps +gaps inner $inner_gaps +gaps outer $outer_gaps + +# border color +# pywal-start +client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577 +client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a +client.unfocused #333333 #222222 #888888 #292d2e #222222 +client.urgent #2f343a #900000 #ffffff #900000 #900000 +client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c + +client.background #ffffff +# pywal-end + +# font +font pango:Fira Code 9 + +# no idea what these do but they were in here by default +exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork +exec --no-startup-id nm-applet +set $refresh_i3status killall -SIGUSR1 i3status + +# Voulme keybinds +bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +5% && $refresh_i3status +bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5% && $refresh_i3status +bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle && $refresh_i3status + +# window interactions +floating_modifier $mod +bindsym $mod+q kill + +# Replace window decorations with a border +# for_window [class=".*"] border pixel 1 + +# Keybinds +bindsym $mod+Escape exec "dm-tool lock" +bindsym $mod+space exec "rofi -show drun -show-icons" +bindsym $mod+Return exec konsole +bindsym Print exec "flameshot gui" + +bindsym XF86AudioPlay exec "playerctl play-pause" +bindsym XF86AudioNext exec "playerctl next" +bindsym XF86AudioPrev exec "playerctl previous" + +# Gaps +bindsym $mod+i gaps inner current plus 5 +bindsym $mod+Shift+i gaps inner current minus 5 +bindsym $mod+o gaps outer current plus 5 +bindsym $mod+Shift+o gaps outer current minus 5 +bindsym $mod+n gaps inner current set -1; gaps outer current set 0 +bindsym $mod+d gaps inner current set $inner_gaps; gaps outer current set $outer_gaps; +bindsym $mod+p gaps top current set $polybar_gap + +# Autostart +exec "polybar main -c ~/.config/polybar/config.ini &" +exec "node ~/scripts/pywal/wall.js" +exec "exec picom &" + +exec "deadd-notification-center &" + +exec "node ~/pester/index.js &" +exec "flameshot &" +exec "dropbox &" +# exec "teams &" + +# change focus +bindsym $mod+h focus left +bindsym $mod+j focus down +bindsym $mod+k focus up +bindsym $mod+l focus right + +# move focused window +bindsym $mod+Shift+h move left +bindsym $mod+Shift+j move down +bindsym $mod+Shift+k move up +bindsym $mod+Shift+l move right + +# resize focused window +bindsym $mod+bracketright resize grow height 10 px +bindsym $mod+bracketleft resize shrink height 10 px +bindsym $mod+period resize grow width 10 px +bindsym $mod+comma resize shrink width 10 px + +# split in horizontal orientation +bindsym $mod+bar split h +bindsym $mod+minus split v + +# enter fullscreen mode for the focused container +bindsym $mod+f fullscreen toggle + +# toggle tiling / floating +bindsym $mod+Shift+space floating toggle + +# change focus between tiling / floating windows +# bindsym $mod+space focus mode_toggle + +# focus the parent container +bindsym $mod+a focus parent + +# focus the child container +#bindsym $mod+d focus child + +# workspaces +set $ws1 "1" +set $ws2 "2" +set $ws3 "3" +set $ws4 "4" + +# switch to workspace +bindsym $mod+1 workspace number $ws1 +bindsym $mod+2 workspace number $ws2 +bindsym $mod+3 workspace number $ws3 +bindsym $mod+4 workspace number $ws4 + +# move focused container to workspace +bindsym $mod+Shift+1 move container to workspace number $ws1 +bindsym $mod+Shift+2 move container to workspace number $ws2 +bindsym $mod+Shift+3 move container to workspace number $ws3 +bindsym $mod+Shift+4 move container to workspace number $ws4 + +# reload the configuration file +bindsym $mod+Shift+c reload +# restart i3 inplace (preserves your layout/session, can be used to upgrade i3) +bindsym $mod+Shift+r restart +# exit i3 (logs you out of your X session) +bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'" + +bar { + mode hide + hidden_state hide + modifier none + tray_output none +} diff --git a/.config/konsolerc b/.config/konsolerc new file mode 100644 index 0000000..c42e4b7 --- /dev/null +++ b/.config/konsolerc @@ -0,0 +1,25 @@ +[Desktop Entry] +DefaultProfile=Loekaars.profile + +[DownloadDialog Settings] +Height 1080=400 +Width 1920=700 + +[Favorite Profiles] +Favorites= + +[KonsoleWindow] +SaveGeometryOnExit=false +ShowMenuBarByDefault=false + +[MainWindow] +MenuBar=Disabled +State=AAAA/wAAAAD9AAAAAAAAB2oAAAQSAAAABAAAAAQAAAAIAAAACPwAAAAA +ToolBarsMovable=Disabled + +[Notification Messages] +CloseAllTabs=true +ShowPasteUnprintableWarning=false + +[Shortcut Schemes] +Current Scheme=Default diff --git a/.config/nvim/autoload/plug.vim b/.config/nvim/autoload/plug.vim new file mode 100644 index 0000000..25be27f --- /dev/null +++ b/.config/nvim/autoload/plug.vim @@ -0,0 +1,2665 @@ +" vim-plug: Vim plugin manager +" ============================ +" +" Download plug.vim and put it in ~/.vim/autoload +" +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim +" +" Edit your .vimrc +" +" call plug#begin('~/.vim/plugged') +" +" " Make sure you use single quotes +" +" " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align +" Plug 'junegunn/vim-easy-align' +" +" " Any valid git URL is allowed +" Plug 'https://github.com/junegunn/vim-github-dashboard.git' +" +" " Multiple Plug commands can be written in a single line using | separators +" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' +" +" " On-demand loading +" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } +" Plug 'tpope/vim-fireplace', { 'for': 'clojure' } +" +" " Using a non-master branch +" Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } +" +" " Using a tagged release; wildcard allowed (requires git 1.9.2 or above) +" Plug 'fatih/vim-go', { 'tag': '*' } +" +" " Plugin options +" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } +" +" " Plugin outside ~/.vim/plugged with post-update hook +" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } +" +" " Unmanaged plugin (manually installed and updated) +" Plug '~/my-prototype-plugin' +" +" " Initialize plugin system +" call plug#end() +" +" Then reload .vimrc and :PlugInstall to install plugins. +" +" Plug options: +" +"| Option | Description | +"| ----------------------- | ------------------------------------------------ | +"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use | +"| `rtp` | Subdirectory that contains Vim plugin | +"| `dir` | Custom directory for the plugin | +"| `as` | Use different name for the plugin | +"| `do` | Post-update hook (string or funcref) | +"| `on` | On-demand loading: Commands or ``-mappings | +"| `for` | On-demand loading: File types | +"| `frozen` | Do not update unless explicitly specified | +" +" More information: https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2017 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +if s:is_win && &shellslash + set noshellslash + let s:me = resolve(expand(':p')) + set shellslash +else + let s:me = resolve(expand(':p')) +endif +let s:base_spec = { 'branch': 'master', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +if s:is_win + function! s:plug_call(fn, ...) + let shellslash = &shellslash + try + set noshellslash + return call(a:fn, a:000) + finally + let &shellslash = shellslash + endtry + endfunction +else + function! s:plug_call(fn, ...) + return call(a:fn, a:000) + endfunction +endif + +function! s:plug_getcwd() + return s:plug_call('getcwd') +endfunction + +function! s:plug_fnamemodify(fname, mods) + return s:plug_call('fnamemodify', a:fname, a:mods) +endfunction + +function! s:plug_expand(fmt) + return s:plug_call('expand', a:fmt, 1) +endfunction + +function! s:plug_tempname() + return s:plug_call('tempname') +endfunction + +function! plug#begin(...) + if a:0 > 0 + let s:plug_home_org = a:1 + let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif !empty(&rtp) + let home = s:path(split(&rtp, ',')[0]) . '/plugged' + else + return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') + endif + if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp + return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') + endif + + let g:plug_home = home + let g:plugs = {} + let g:plugs_order = [] + let s:triggers = {} + + call s:define_commands() + return 1 +endfunction + +function! s:define_commands() + command! -nargs=+ -bar Plug call plug#() + if !executable('git') + return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') + endif + if has('win32') + \ && &shellslash + \ && (&shell =~# 'cmd\.exe' || &shell =~# 'powershell\.exe') + return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.') + endif + if !has('nvim') + \ && (has('win32') || has('win32unix')) + \ && !has('multi_byte') + return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.') + endif + command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) + command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) + command! -nargs=0 -bar -bang PlugClean call s:clean(0) + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif + command! -nargs=0 -bar PlugStatus call s:status() + command! -nargs=0 -bar PlugDiff call s:diff() + command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) +endfunction + +function! s:to_a(v) + return type(a:v) == s:TYPE.list ? a:v : [a:v] +endfunction + +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + +function! s:glob(from, pattern) + return s:lines(globpath(a:from, a:pattern)) +endfunction + +function! s:source(from, ...) + let found = 0 + for pattern in a:000 + for vim in s:glob(a:from, pattern) + execute 'source' s:esc(vim) + let found = 1 + endfor + endfor + return found +endfunction + +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + +function! s:ask(message, ...) + call inputsave() + echohl WarningMsg + let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) + echohl None + call inputrestore() + echo "\r" + return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 +endfunction + +function! s:ask_no_interrupt(...) + try + return call('s:ask', a:000) + catch + return 0 + endtry +endfunction + +function! s:lazy(plug, opt) + return has_key(a:plug, a:opt) && + \ (empty(s:to_a(a:plug[a:opt])) || + \ !isdirectory(a:plug.dir) || + \ len(s:glob(s:rtp(a:plug), 'plugin')) || + \ len(s:glob(s:rtp(a:plug), 'after/plugin'))) +endfunction + +function! plug#end() + if !exists('g:plugs') + return s:err('plug#end() called without calling plug#begin() first') + endif + + if exists('#PlugLOD') + augroup PlugLOD + autocmd! + augroup END + augroup! PlugLOD + endif + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } + + if exists('g:did_load_filetypes') + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or ``.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) +endfunction + +if s:is_win + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction + + " Copied from fzf + function! s:wrap_cmds(cmds) + let cmds = [ + \ '@echo off', + \ 'setlocal enabledelayedexpansion'] + \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) + \ + ['endlocal'] + if has('iconv') + if !exists('s:codepage') + let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0) + endif + return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage)) + endif + return map(cmds, 'v:val."\r"') + endfunction + + function! s:batchfile(cmd) + let batchfile = s:plug_tempname().'.bat' + call writefile(s:wrap_cmds(a:cmd), batchfile) + let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0}) + if &shell =~# 'powershell\.exe' + let cmd = '& ' . cmd + endif + return [batchfile, cmd] + endfunction +else + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]) + for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) +endfunction + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^', "\", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + if type == s:TYPE.string + let opts.tag = a:arg + elseif type == s:TYPE.dict + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(s:plug_expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(s:plug_expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugDash /^-/ + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', 'vertical topleft new') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap R :call retry() + nnoremap D :PlugDiff + nnoremap S :PlugStatus + nnoremap U :call status_update() + xnoremap U :call status_update() + nnoremap ]] :silent! call section('') + nnoremap [[ :silent! call section('b') +endfunction + +function! s:prepare(...) + if empty(s:plug_getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort() + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap q :if b:plug_preview==1pcendifbd + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap ' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + if exists('+colorcolumn') + setlocal colorcolumn= + endif + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if !s:is_win + set shell=sh + endif + if a:swap + if &shell =~# 'powershell\.exe' || &shell =~# 'pwsh$' + let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s' + elseif &shell =~# 'sh' || &shell =~# 'cmd\.exe' + set shellredir=>%s\ 2>&1 + endif + endif + return prev +endfunction + +function! s:bang(cmd, ...) + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let [batchfile, cmd] = s:batchfile(cmd) + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\\" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir)) +endfunction + +function! s:do(pull, force, todo) + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + call s:load_plugin(spec) + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:system(['git', 'rev-parse', 'HEAD'], a:spec.dir) + if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) + let output = s:system( + \ 'git fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir) + endif + return output +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(copy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + let s:clone_opt = [] + if get(g:, 'plug_shallow', 1) + call extend(s:clone_opt, ['--depth', '1']) + if s:git_version_requirement(1, 7, 10) + call add(s:clone_opt, '--no-single-branch') + endif + endif + + if has('win32unix') || has('wsl') + call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input']) + endif + + let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : '' + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let out = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir) + else + let branch = get(spec, 'branch', 'master') + call s:log4(name, 'Merging origin/'.s:esc(branch)) + let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1' + \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir) + endif + if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if v:shell_error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:job_abort() + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:rm_rf(g:plugs[name].dir) + endif + endfor + let s:jobs = {} +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + call s:log(bullet, self.name, result) + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort() + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return (a:event == 'stdout' || a:event == 'stderr') ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, cmd, opts) + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], + \ 'new': get(a:opts, 'new', 0) } + let s:jobs[a:name] = job + + if s:nvim + if has_key(a:opts, 'dir') + let job.cwd = a:opts.dir + endif + let argv = a:cmd + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_stderr': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = s:plug_call('jobstart', argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})')) + if has_key(a:opts, 'dir') + let cmd = s:with_cd(cmd, a:opts.dir, 0) + endif + let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd] + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'err_mode': 'raw', + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = s:jobs[a:name] + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + let s:update.bar .= job.error ? 'x' : '=' + + let bullet = job.error ? 'x' : '-' + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + call s:log(bullet, a:name, empty(result) ? 'OK' : result) + call s:bar() + + call remove(s:jobs, a:name) +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + let max = line('$') + for i in range(4, max > 4 ? max : 4) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, max > 5 ? max : 5) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + let new = empty(globpath(spec.dir, '.git', 1)) + + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + + let has_tag = has_key(spec, 'tag') + if !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let cmd = ['git', 'fetch'] + if has_tag && !empty(globpath(spec.dir, '.git/shallow')) + call extend(cmd, ['--depth', '99999999']) + endif + if !empty(prog) + call add(cmd, prog) + endif + call s:spawn(name, cmd, { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + let cmd = ['git', 'clone'] + if !has_tag + call extend(cmd, s:clone_opt) + endif + if !empty(prog) + call add(cmd, prog) + endif + call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt')) +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt').join(' ') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg, script) + let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g') + return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g') +endfunction + +function! s:shellesc_ps1(arg) + return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'" +endfunction + +function! s:shellesc_sh(arg) + return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'" +endfunction + +" Escape the shell argument based on the shell. +" Vim and Neovim's shellescape() are insufficient. +" 1. shellslash determines whether to use single/double quotes. +" Double-quote escaping is fragile for cmd.exe. +" 2. It does not work for powershell. +" 3. It does not work for *sh shells if the command is executed +" via cmd.exe (ie. cmd.exe /c sh -c command command_args) +" 4. It does not support batchfile syntax. +" +" Accepts an optional dictionary with the following keys: +" - shell: same as Vim/Neovim 'shell' option. +" If unset, fallback to 'cmd.exe' on Windows or 'sh'. +" - script: If truthy and shell is cmd.exe, escape for batchfile syntax. +function! plug#shellescape(arg, ...) + if a:arg =~# '^[A-Za-z0-9_/:.-]\+$' + return a:arg + endif + let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {} + let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh') + let script = get(opts, 'script', 1) + if shell =~# 'cmd\.exe' + return s:shellesc_cmd(a:arg, script) + elseif shell =~# 'powershell\.exe' || shell =~# 'pwsh$' + return s:shellesc_ps1(a:arg) + endif + return s:shellesc_sh(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir, ...) + let script = a:0 > 0 ? a:1 : 1 + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd) +endfunction + +function! s:system(cmd, ...) + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + if type(a:cmd) == s:TYPE.list + " Neovim's system() supports list argument to bypass the shell + " but it cannot set the working directory for the command. + " Assume that the command does not rely on the shell. + if has('nvim') && a:0 == 0 + return system(a:cmd) + endif + let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})')) + if &shell =~# 'powershell\.exe' + let cmd = '& ' . cmd + endif + else + let cmd = a:cmd + endif + if a:0 > 0 + let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list) + endif + if s:is_win && type(a:cmd) != s:TYPE.list + let [batchfile, cmd] = s:batchfile(cmd) + endif + return system(cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir)) + let remote = result[-1] + if v:shell_error + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif a:check_branch && has_key(a:spec, 'commit') + let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) + let sha = result[-1] + if v:shell_error + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif a:check_branch + let branch = result[0] + " Check tag + if has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + " Check branch + elseif a:spec.branch !=# branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ branch, a:spec.branch) + endif + if empty(err) + let [ahead, behind] = split(s:lastline(s:system([ + \ 'git', 'rev-list', '--count', '--left-right', + \ printf('HEAD...origin/%s', a:spec.branch) + \ ], a:spec.dir)), '\t') + if !v:shell_error && ahead + if behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind) + else + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ a:spec.branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + call s:system(s:is_win + \ ? 'rmdir /S /Q '.plug#shellescape(a:dir) + \ : ['rm', '-rf', a:dir]) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap d :set opfunc=delete_opg@ + nmap dd d_ + xnoremap d :call delete_op(visualmode(), 1) + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + call s:rm_rf(line[2:]) + setlocal modifiable + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + call setline(4, printf('Removed %d directories.', s:clean_count)) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = s:plug_tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp]) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap L :call status_load(line('.')) + xnoremap L :call status_load(line('.')) + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if exists('g:plug_pwindow') && !s:is_preview_window_open() + execute g:plug_pwindow + execute 'e' sha + else + execute 'pedit' sha + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha + if s:is_win + let [batchfile, cmd] = s:batchfile(cmd) + endif + execute 'silent %!' cmd + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry + setlocal nomodifiable + nnoremap q :q + wincmd p +endfunction + +function! s:section(flags) + call search('\(^[x-] \)\@<=[^:]\+:', a:flags) +endfunction + +function! s:format_git_log(line) + let indent = ' ' + let tokens = split(a:line, nr2char(1)) + if len(tokens) != 5 + return indent.substitute(a:line, '\s*$', '', '') + endif + let [graph, sha, refs, subject, date] = tokens + let tag = matchstr(refs, 'tag: [^,)]\+') + let tag = empty(tag) ? ' ' : ' ('.tag.') ' + return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) +endfunction + +function! s:append_ul(lnum, text) + call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) +endfunction + +function! s:diff() + call s:prepare() + call append(0, ['Collecting changes ...', '']) + let cnts = [0, 0] + let bar = '' + let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') + call s:progress_bar(2, bar, len(total)) + for origin in [1, 0] + let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) + if empty(plugs) + continue + endif + call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') + for [k, v] in plugs + let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..' + let cmd = ['git', 'log', '--graph', '--color=never'] + if s:git_version_requirement(2, 10, 0) + call add(cmd, '--no-show-signature') + endif + call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range]) + if has_key(v, 'rtp') + call extend(cmd, ['--', v.rtp]) + endif + let diff = s:system_chomp(cmd, v.dir) + if !empty(diff) + let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' + call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) + let cnts[origin] += 1 + endif + let bar .= '=' + call s:progress_bar(2, bar, len(total)) + normal! 2G + redraw + endfor + if !cnts[origin] + call append(5, ['', 'N/A']) + endif + endfor + call setline(1, printf('%d plugin(s) updated.', cnts[0]) + \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) + + if cnts[0] || cnts[1] + nnoremap (plug-preview) :silent! call preview_commit() + if empty(maparg("\", 'n')) + nmap (plug-preview) + endif + if empty(maparg('o', 'n')) + nmap o (plug-preview) + endif + endif + if cnts[0] + nnoremap X :call revert() + echo "Press 'X' on each block to revert the update" + endif + normal! gg + setlocal nomodifiable +endfunction + +function! s:revert() + if search('^Pending updates', 'bnW') + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' + return + endif + + call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir) + setlocal modifiable + normal! "_dap + setlocal nomodifiable + echo 'Reverted' +endfunction + +function! s:snapshot(force, ...) abort + call s:prepare() + setf vim + call append(0, ['" Generated by vim-plug', + \ '" '.strftime("%c"), + \ '" :source this file in vim to restore the snapshot', + \ '" or execute: vim -S snapshot.vim', + \ '', '', 'PlugUpdate!']) + 1 + let anchor = line('$') - 3 + let names = sort(keys(filter(copy(g:plugs), + \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = s:system_chomp(['git', 'rev-parse', '--short', 'HEAD'], g:plugs[name].dir) + if !empty(sha) + call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) + redraw + endif + endfor + + if a:0 > 0 + let fn = s:plug_expand(a:1) + if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) + return + endif + call writefile(getline(1, '$'), fn) + echo 'Saved as '.a:1 + silent execute 'e' s:esc(fn) + setf vim + endif +endfunction + +function! s:split_rtp() + return split(&rtp, '\\\@`-mappings | +"| `for` | On-demand loading: File types | +"| `frozen` | Do not update unless explicitly specified | +" +" More information: https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2017 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +if s:is_win && &shellslash + set noshellslash + let s:me = resolve(expand(':p')) + set shellslash +else + let s:me = resolve(expand(':p')) +endif +let s:base_spec = { 'branch': 'master', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +if s:is_win + function! s:plug_call(fn, ...) + let shellslash = &shellslash + try + set noshellslash + return call(a:fn, a:000) + finally + let &shellslash = shellslash + endtry + endfunction +else + function! s:plug_call(fn, ...) + return call(a:fn, a:000) + endfunction +endif + +function! s:plug_getcwd() + return s:plug_call('getcwd') +endfunction + +function! s:plug_fnamemodify(fname, mods) + return s:plug_call('fnamemodify', a:fname, a:mods) +endfunction + +function! s:plug_expand(fmt) + return s:plug_call('expand', a:fmt, 1) +endfunction + +function! s:plug_tempname() + return s:plug_call('tempname') +endfunction + +function! plug#begin(...) + if a:0 > 0 + let s:plug_home_org = a:1 + let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif !empty(&rtp) + let home = s:path(split(&rtp, ',')[0]) . '/plugged' + else + return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') + endif + if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp + return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') + endif + + let g:plug_home = home + let g:plugs = {} + let g:plugs_order = [] + let s:triggers = {} + + call s:define_commands() + return 1 +endfunction + +function! s:define_commands() + command! -nargs=+ -bar Plug call plug#() + if !executable('git') + return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') + endif + if has('win32') + \ && &shellslash + \ && (&shell =~# 'cmd\.exe' || &shell =~# 'powershell\.exe') + return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.') + endif + if !has('nvim') + \ && (has('win32') || has('win32unix')) + \ && !has('multi_byte') + return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.') + endif + command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) + command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) + command! -nargs=0 -bar -bang PlugClean call s:clean(0) + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif + command! -nargs=0 -bar PlugStatus call s:status() + command! -nargs=0 -bar PlugDiff call s:diff() + command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) +endfunction + +function! s:to_a(v) + return type(a:v) == s:TYPE.list ? a:v : [a:v] +endfunction + +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + +function! s:glob(from, pattern) + return s:lines(globpath(a:from, a:pattern)) +endfunction + +function! s:source(from, ...) + let found = 0 + for pattern in a:000 + for vim in s:glob(a:from, pattern) + execute 'source' s:esc(vim) + let found = 1 + endfor + endfor + return found +endfunction + +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + +function! s:ask(message, ...) + call inputsave() + echohl WarningMsg + let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) + echohl None + call inputrestore() + echo "\r" + return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 +endfunction + +function! s:ask_no_interrupt(...) + try + return call('s:ask', a:000) + catch + return 0 + endtry +endfunction + +function! s:lazy(plug, opt) + return has_key(a:plug, a:opt) && + \ (empty(s:to_a(a:plug[a:opt])) || + \ !isdirectory(a:plug.dir) || + \ len(s:glob(s:rtp(a:plug), 'plugin')) || + \ len(s:glob(s:rtp(a:plug), 'after/plugin'))) +endfunction + +function! plug#end() + if !exists('g:plugs') + return s:err('plug#end() called without calling plug#begin() first') + endif + + if exists('#PlugLOD') + augroup PlugLOD + autocmd! + augroup END + augroup! PlugLOD + endif + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } + + if exists('g:did_load_filetypes') + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or ``.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) +endfunction + +if s:is_win + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction + + " Copied from fzf + function! s:wrap_cmds(cmds) + let cmds = [ + \ '@echo off', + \ 'setlocal enabledelayedexpansion'] + \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) + \ + ['endlocal'] + if has('iconv') + if !exists('s:codepage') + let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0) + endif + return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage)) + endif + return map(cmds, 'v:val."\r"') + endfunction + + function! s:batchfile(cmd) + let batchfile = s:plug_tempname().'.bat' + call writefile(s:wrap_cmds(a:cmd), batchfile) + let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0}) + if &shell =~# 'powershell\.exe' + let cmd = '& ' . cmd + endif + return [batchfile, cmd] + endfunction +else + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]) + for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) +endfunction + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^', "\", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + if type == s:TYPE.string + let opts.tag = a:arg + elseif type == s:TYPE.dict + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(s:plug_expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(s:plug_expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugDash /^-/ + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', 'vertical topleft new') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap R :call retry() + nnoremap D :PlugDiff + nnoremap S :PlugStatus + nnoremap U :call status_update() + xnoremap U :call status_update() + nnoremap ]] :silent! call section('') + nnoremap [[ :silent! call section('b') +endfunction + +function! s:prepare(...) + if empty(s:plug_getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort() + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap q :if b:plug_preview==1pcendifbd + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap ' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + if exists('+colorcolumn') + setlocal colorcolumn= + endif + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if !s:is_win && a:swap + set shell=sh shellredir=>%s\ 2>&1 + endif + return prev +endfunction + +function! s:bang(cmd, ...) + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let [batchfile, cmd] = s:batchfile(cmd) + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\\" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir)) +endfunction + +function! s:do(pull, force, todo) + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:system('git rev-parse HEAD', a:spec.dir) + if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) + let output = s:system( + \ 'git fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir) + endif + return output +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(copy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + let s:clone_opt = get(g:, 'plug_shallow', 1) ? + \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' + + if has('win32unix') || has('wsl') + let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input' + endif + + let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : '' + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let out = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir) + else + let branch = get(spec, 'branch', 'master') + call s:log4(name, 'Merging origin/'.s:esc(branch)) + let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1' + \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir) + endif + if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if v:shell_error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:job_abort() + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:rm_rf(g:plugs[name].dir) + endif + endfor + let s:jobs = {} +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + call s:log(bullet, self.name, result) + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort() + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return a:event == 'stdout' ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, cmd, opts) + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], + \ 'new': get(a:opts, 'new', 0) } + let s:jobs[a:name] = job + let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir, 0) : a:cmd + let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd] + + if s:nvim + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = s:plug_call('jobstart', argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', [cmd])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = s:jobs[a:name] + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + let s:update.bar .= job.error ? 'x' : '=' + + let bullet = job.error ? 'x' : '-' + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + call s:log(bullet, a:name, empty(result) ? 'OK' : result) + call s:bar() + + call remove(s:jobs, a:name) +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + let max = line('$') + for i in range(4, max > 4 ? max : 4) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, max > 5 ? max : 5) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + let new = empty(globpath(spec.dir, '.git', 1)) + + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + + let has_tag = has_key(spec, 'tag') + if !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' + call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + call s:spawn(name, + \ printf('git clone %s %s %s %s 2>&1', + \ has_tag ? '' : s:clone_opt, + \ prog, + \ plug#shellescape(spec.uri, {'script': 0}), + \ plug#shellescape(s:trim(spec.dir), {'script': 0})), { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = vim.eval('s:clone_opt') +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg, script) + let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g') + return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g') +endfunction + +function! s:shellesc_ps1(arg) + return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'" +endfunction + +function! s:shellesc_sh(arg) + return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'" +endfunction + +function! plug#shellescape(arg, ...) + let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {} + let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh') + let script = get(opts, 'script', 1) + if shell =~# 'cmd\.exe' + return s:shellesc_cmd(a:arg, script) + elseif shell =~# 'powershell\.exe' || shell =~# 'pwsh$' + return s:shellesc_ps1(a:arg) + endif + return s:shellesc_sh(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir, ...) + let script = a:0 > 0 ? a:1 : 1 + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd) +endfunction + +function! s:system(cmd, ...) + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let [batchfile, cmd] = s:batchfile(cmd) + endif + return system(cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir)) + let remote = result[-1] + if v:shell_error + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif a:check_branch && has_key(a:spec, 'commit') + let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) + let sha = result[-1] + if v:shell_error + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif a:check_branch + let branch = result[0] + " Check tag + if has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + " Check branch + elseif a:spec.branch !=# branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ branch, a:spec.branch) + endif + if empty(err) + let [ahead, behind] = split(s:lastline(s:system(printf( + \ 'git rev-list --count --left-right HEAD...origin/%s', + \ a:spec.branch), a:spec.dir)), '\t') + if !v:shell_error && ahead + if behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind) + else + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ a:spec.branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . plug#shellescape(a:dir)) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap d :set opfunc=delete_opg@ + nmap dd d_ + xnoremap d :call delete_op(visualmode(), 1) + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + call s:rm_rf(line[2:]) + setlocal modifiable + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + call setline(4, printf('Removed %d directories.', s:clean_count)) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = s:plug_tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(printf('git clone --depth 1 %s %s', plug#shellescape(s:plug_src), plug#shellescape(tmp))) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap L :call status_load(line('.')) + xnoremap L :call status_load(line('.')) + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if exists('g:plug_pwindow') && !s:is_preview_window_open() + execute g:plug_pwindow + execute 'e' sha + else + execute 'pedit' sha + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha + if s:is_win + let [batchfile, cmd] = s:batchfile(cmd) + endif + execute 'silent %!' cmd + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry + setlocal nomodifiable + nnoremap q :q + wincmd p +endfunction + +function! s:section(flags) + call search('\(^[x-] \)\@<=[^:]\+:', a:flags) +endfunction + +function! s:format_git_log(line) + let indent = ' ' + let tokens = split(a:line, nr2char(1)) + if len(tokens) != 5 + return indent.substitute(a:line, '\s*$', '', '') + endif + let [graph, sha, refs, subject, date] = tokens + let tag = matchstr(refs, 'tag: [^,)]\+') + let tag = empty(tag) ? ' ' : ' ('.tag.') ' + return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) +endfunction + +function! s:append_ul(lnum, text) + call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) +endfunction + +function! s:diff() + call s:prepare() + call append(0, ['Collecting changes ...', '']) + let cnts = [0, 0] + let bar = '' + let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') + call s:progress_bar(2, bar, len(total)) + for origin in [1, 0] + let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) + if empty(plugs) + continue + endif + call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') + for [k, v] in plugs + let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..' + let cmd = 'git log --graph --color=never ' + \ . (s:git_version_requirement(2, 10, 0) ? '--no-show-signature ' : '') + \ . join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 'plug#shellescape(v:val)')) + if has_key(v, 'rtp') + let cmd .= ' -- '.plug#shellescape(v.rtp) + endif + let diff = s:system_chomp(cmd, v.dir) + if !empty(diff) + let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' + call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) + let cnts[origin] += 1 + endif + let bar .= '=' + call s:progress_bar(2, bar, len(total)) + normal! 2G + redraw + endfor + if !cnts[origin] + call append(5, ['', 'N/A']) + endif + endfor + call setline(1, printf('%d plugin(s) updated.', cnts[0]) + \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) + + if cnts[0] || cnts[1] + nnoremap (plug-preview) :silent! call preview_commit() + if empty(maparg("\", 'n')) + nmap (plug-preview) + endif + if empty(maparg('o', 'n')) + nmap o (plug-preview) + endif + endif + if cnts[0] + nnoremap X :call revert() + echo "Press 'X' on each block to revert the update" + endif + normal! gg + setlocal nomodifiable +endfunction + +function! s:revert() + if search('^Pending updates', 'bnW') + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' + return + endif + + call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir) + setlocal modifiable + normal! "_dap + setlocal nomodifiable + echo 'Reverted' +endfunction + +function! s:snapshot(force, ...) abort + call s:prepare() + setf vim + call append(0, ['" Generated by vim-plug', + \ '" '.strftime("%c"), + \ '" :source this file in vim to restore the snapshot', + \ '" or execute: vim -S snapshot.vim', + \ '', '', 'PlugUpdate!']) + 1 + let anchor = line('$') - 3 + let names = sort(keys(filter(copy(g:plugs), + \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir) + if !empty(sha) + call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) + redraw + endif + endfor + + if a:0 > 0 + let fn = s:plug_expand(a:1) + if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) + return + endif + call writefile(getline(1, '$'), fn) + echo 'Saved as '.a:1 + silent execute 'e' s:esc(fn) + setf vim + endif +endfunction + +function! s:split_rtp() + return split(&rtp, '\\\@"}\end{"} + diff --git a/.config/nvim/init.vim b/.config/nvim/init.vim new file mode 100644 index 0000000..bd53196 --- /dev/null +++ b/.config/nvim/init.vim @@ -0,0 +1,246 @@ +let mapleader = " " +set number relativenumber +set tabstop=4 +set wrap +set ai +set shiftwidth=4 +set termguicolors +set shortmess=I +set splitbelow splitright +set noshowmode +set ignorecase +set smartcase +set timeoutlen=500 +set conceallevel=0 +set mouse=a +set linebreak " set wrap but don't wrap inside words +set viminfo+='1000,n/home/loek/.local/nvim/viminfo +let g:sneak#label = 1 +let g:which_key_map = {} +let g:airline_powerline_fonts = 1 +let g:minimap_highlight='Visual' +hi! link CocFloating SneakScope +cabbrev help tab help + +if ! filereadable(expand('~/.config/nvim/autoload/plug.vim')) + echo "Downloading junegunn/vim-plug to manage plugins..." + silent !mkdir -p ~/.config/nvim/autoload/ + silent !curl "https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" > ~/.config/nvim/autoload/plug.vim + autocmd VimEnter * PlugInstall +endif + +" plugged +call plug#begin('~/.config/nvim/plugged') +" quality of life +Plug 'jiangmiao/auto-pairs' +Plug 'tpope/vim-surround' +Plug 'Chiel92/vim-autoformat' +Plug 'itchyny/lightline.vim' +Plug 'terryma/vim-multiple-cursors' +" Plug 'ap/vim-css-color' " color name highlighter +Plug 'vim-scripts/colorizer' " better highlighter? +Plug 'aurieh/discord.nvim', { 'do': ':UpdateRemotePlugins'} +Plug 'AndrewRadev/tagalong.vim' +Plug 'tpope/vim-commentary' +Plug 'liuchengxu/vim-which-key' +Plug 'justinmk/vim-sneak' +Plug 'jbgutierrez/vim-better-comments' +Plug 'junegunn/goyo.vim' +Plug 'othree/eregex.vim' +Plug 'psliwka/vim-smoothie' + +" language plugins +Plug 'lervag/vimtex' +Plug 'pangloss/vim-javascript' +Plug 'hail2u/vim-css3-syntax' +Plug 'octol/vim-cpp-enhanced-highlight' +Plug 'vim-scripts/c.vim' + +" 'vim=ide' +Plug 'neoclide/coc.nvim', {'branch': 'release'} +Plug 'ryanoasis/vim-devicons' "Icons for filetypes +Plug 'junegunn/fzf.vim' +Plug '/usr/local/opt/fzf' + +" themes +Plug 'arzg/vim-colors-xcode' +Plug 'sainnhe/sonokai' +Plug 'bluz71/vim-nightfly-guicolors' +Plug 'Mcmartelle/vim-monokai-bold' +Plug 'ntk148v/vim-horizon' +Plug 'NLKNguyen/papercolor-theme' +Plug 'scheakur/vim-scheakur' +Plug 'mkarmona/materialbox' +Plug 'morhetz/gruvbox' +call plug#end() + +" keybinds +tnoremap + +nnoremap tn :tabnext +nnoremap tp :tabprevious +nnoremap tt :tabnew + +nmap o + +nnoremap :m +1 +nnoremap :m -2 + +" leader keybindings +nnoremap :WhichKey '' + +map p "+p +map y "+y +let g:which_key_map.p = 'x11-paste' +let g:which_key_map.y = 'x11-yank' + +map h :noh +let g:which_key_map.h = 'no-highlighting' + +map b :Autoformat +let g:which_key_map.b = 'format-file' + +map w /\s\+$ +let g:which_key_map.w = 'trailing-whitespace' + +map dv :!opout % +map dc :w! !compiler % +let g:which_key_map.d = { + \ 'name': '+document', + \ 'v': 'view', + \ 'c': 'compile' + \ } + +map ts :sp term://zshi +map tv :vsp term://zshi +map tt :tabnew term://zshi +let g:which_key_map.t = { + \ 'name': '+term', + \ 's': 'split', + \ 'v': 'vsplit', + \ 't': 'tab' + \ } + +map .v :tabnew ~/.config/nvim/init.vim + +map .c :tabnew ~/.config/picom.conf +map .z :tabnew ~/.zshrc +map .i :tabnew ~/.config/i3/config +map .p :tabnew ~/.config/polybar/config.ini +let g:which_key_map['.'] = { + \ 'name': '+config', + \ 'v': 'vim', + \ 'z': 'zsh', + \ 'i': 'i3', + \ 'c': 'picom', + \ 'p': 'polybar' + \ } + +colorscheme xcodedark +let g:lightline = { + \ 'colorscheme': 'pywal', + \ 'separator': { 'left': '', 'right': '' }, + \ 'mode_map': { + \ 'n' : 'NORM', + \ 'i' : 'INS', + \ 'R' : 'REP', + \ 'v' : 'VIS', + \ 'V' : 'V-L', + \ "\": 'V-B', + \ 'c' : 'CMD', + \ 's' : 'SEL', + \ 'S' : 'S-L', + \ "\": 'S-B', + \ 't': 'TERM' + \ }, + \ 'active': { + \ 'left': [ + \ [ 'mode', 'paste' ], + \ [ 'readonly', 'filename', 'modified'] + \ ], + \ 'right': [ + \ [ 'lineinfo' ], + \ [ 'filetype' ] + \ ] + \ }, + \ } +source $HOME/.config/nvim/pywal/pywal.vim + +" vimtex config +let g:tex_flavor = 'latex' +let g:vimtex_compiler_latexmk = { + \ 'backend' : 'nvim', + \ 'background' : 1, + \ 'build_dir' : '', + \ 'callback' : 1, + \ 'continuous' : 1, + \ 'executable' : 'latexmk', + \ 'engine' : 'xelatex', + \ 'hooks' : [], + \ 'options' : [ + \ '-xelatex', + \ '-file-line-error', + \ '-synctex=1', + \ '-interaction=nonstopmode', + \ ], + \} + +" auto start compilation (!not tested!) +augroup vimtex_config + autocmd User VimtexEventInitPost silent VimtexCompile +augroup END +" TeX quotes +autocmd FileType tex let b:surround_{char2nr("q")} = "`\r'" +autocmd FileType tex let b:surround_{char2nr('Q')} = "``\r''" + +" coc.vim completion and stuffs +inoremap + \ pumvisible() ? "\" : + \ check_back_space() ? "\" : + \ coc#refresh() +inoremap pumvisible() ? "\" : "\" + +function! s:check_back_space() abort + let col = col('.') - 1 + return !col || getline('.')[col - 1] =~# '\s' +endfunction + +if has('patch8.1.1068') + " Use `complete_info` if your (Neo)Vim version supports it. + inoremap complete_info()["selected"] != "-1" ? "\" : "\u\" +else + imap pumvisible() ? "\" : "\u\" +endif + +" coc code navigation +map cd (coc-definition) +map cy (coc-type-definition) +map ci (coc-implementation) +map cr (coc-references) +map cn (coc-rename) +let g:which_key_map.c = { + \ 'name': '+coc', + \ 'd': 'definition', + \ 'y': 'type-definition', + \ 'i': 'implementation', + \ 'r': 'references', + \ 'n': 'rename' + \ } + +" use for trigger completion +inoremap coc#refresh() + +" auto-comment uit +autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o + +" which key register dict +call which_key#register('', "g:which_key_map") + +" no line numbers in terminal +augroup TerminalStuff + " Clear old autocommands + au! + autocmd TermOpen * setlocal nonumber norelativenumber +augroup END + + diff --git a/.config/nvim/links.sh b/.config/nvim/links.sh new file mode 100644 index 0000000..0bdeb1b --- /dev/null +++ b/.config/nvim/links.sh @@ -0,0 +1,9 @@ +# https://simplyian.com/2014/04/28/how-to-have-language-specific-settings-in-vim/ +# https://vimawesome.com/ +# https://github.com/LukeSmithxyz/voidrice/blob/master/.config/nvim/init.vim +# https://github.com/neoclide/coc.nvim +# https://github.com/jiangmiao/auto-pairs +# https://github.com/ryanoasis/vim-devicons +# https://github.com/prettier/vim-prettier +# https://github.com/DarthOstrich/dotfiles/blob/master/init.vim +# https://github.com/junegunn/vim-plug/wiki/tips#automatic-installation \ No newline at end of file diff --git a/.config/nvim/pywal/base.vim b/.config/nvim/pywal/base.vim new file mode 100644 index 0000000..c25eede --- /dev/null +++ b/.config/nvim/pywal/base.vim @@ -0,0 +1,19 @@ +let s:p = {'normal': {}, 'inactive': {}, 'insert': {}, 'replace': {}, 'visual': {}, 'tabline': {}} +let s:p.normal.left = [ [ s:color6readable, s:color6 ], [ s:color2readable, s:color2 ] ] +let s:p.normal.right = [ [ s:color1readable, s:color1 ], [ s:color2readable, s:color2 ] ] +let s:p.inactive.right = [ [ s:color1readable, s:color1 ], [ s:color2readable, s:color2 ] ] +let s:p.inactive.left = [ [ s:color6readable, s:color6 ], [ s:color2readable, s:color2 ] ] +let s:p.insert.left = [ [ s:color5readable, s:color5 ], [ s:color2readable, s:color2 ] ] +let s:p.replace.left = [ [ s:color4readable, s:color4 ], [ s:color2readable, s:color2 ] ] +let s:p.visual.left = [ [ s:color3readable, s:color3 ], [ s:color2readable, s:color2 ] ] +let s:p.normal.middle = [ [ s:none, s:none ] ] +let s:p.inactive.middle = [ [ s:none, s:none ] ] +let s:p.tabline.left = [ [ s:fg, s:bg ] ] +let s:p.tabline.tabsel = [ [ s:fg, s:bg5 ] ] +let s:p.tabline.middle = [ [ s:none, s:none ] ] +let s:p.tabline.right = copy(s:p.normal.right) +let s:p.normal.error = [ [ s:test, s:test ] ] +let s:p.normal.warning = [ [ s:test, s:test ] ] + +let g:lightline#colorscheme#pywal#palette = lightline#colorscheme#flatten(s:p) + diff --git a/.config/nvim/pywal/colors.vim b/.config/nvim/pywal/colors.vim new file mode 100644 index 0000000..36673b5 --- /dev/null +++ b/.config/nvim/pywal/colors.vim @@ -0,0 +1,46 @@ +hi! Normal guibg=#141415 ctermbg=NONE +hi! EndOfBuffer guibg=#141415 ctermbg=NONE +hi! CocInfoFloat guibg=#28282A ctermbg=NONE + +let s:bg = [ '#141415', 'NONE' ] +let s:fg = [ '#c4c4c4', 'NONE' ] +let s:bg1 = [ '#181819', 'NONE' ] +let s:bg2 = [ '#1C1C1D', 'NONE' ] +let s:bg3 = [ '#202022', 'NONE' ] +let s:bg4 = [ '#242426', 'NONE' ] +let s:bg5 = [ '#28282A', 'NONE' ] +let s:color0 = [ '#141415', 'NONE' ] +let s:color1 = [ '#292b37', 'NONE' ] +let s:color2 = [ '#3c2a27', 'NONE' ] +let s:color3 = [ '#3b3033', 'NONE' ] +let s:color4 = [ '#3f3330', 'NONE' ] +let s:color5 = [ '#4b4240', 'NONE' ] +let s:color6 = [ '#66615a', 'NONE' ] +let s:color7 = [ '#c4c4c4', 'NONE' ] +let s:color8 = [ '#4e4e4f', 'NONE' ] +let s:color9 = [ '#292b37', 'NONE' ] +let s:color10 = [ '#3c2a27', 'NONE' ] +let s:color11 = [ '#3b3033', 'NONE' ] +let s:color12 = [ '#3f3330', 'NONE' ] +let s:color13 = [ '#4b4240', 'NONE' ] +let s:color14 = [ '#66615a', 'NONE' ] +let s:color15 = [ '#c4c4c4', 'NONE' ] +let s:color0readable = [ '#c4c4c4', 'NONE' ] +let s:color1readable = [ '#c4c4c4', 'NONE' ] +let s:color2readable = [ '#c4c4c4', 'NONE' ] +let s:color3readable = [ '#c4c4c4', 'NONE' ] +let s:color4readable = [ '#c4c4c4', 'NONE' ] +let s:color5readable = [ '#c4c4c4', 'NONE' ] +let s:color6readable = [ '#c4c4c4', 'NONE' ] +let s:color7readable = [ '#141415', 'NONE' ] +let s:color8readable = [ '#c4c4c4', 'NONE' ] +let s:color9readable = [ '#c4c4c4', 'NONE' ] +let s:color10readable = [ '#c4c4c4', 'NONE' ] +let s:color11readable = [ '#c4c4c4', 'NONE' ] +let s:color12readable = [ '#c4c4c4', 'NONE' ] +let s:color13readable = [ '#c4c4c4', 'NONE' ] +let s:color14readable = [ '#c4c4c4', 'NONE' ] +let s:color15readable = [ '#141415', 'NONE' ] +let s:none = [ 'NONE', 'NONE' ] +let s:test = [ '#ff00ff', 'NONE' ] + diff --git a/.config/nvim/pywal/pywal.vim b/.config/nvim/pywal/pywal.vim new file mode 100644 index 0000000..ba4c44e --- /dev/null +++ b/.config/nvim/pywal/pywal.vim @@ -0,0 +1,65 @@ +hi! Normal guibg=#141415 ctermbg=NONE +hi! EndOfBuffer guibg=#141415 ctermbg=NONE +hi! CocInfoFloat guibg=#28282A ctermbg=NONE + +let s:bg = [ '#141415', 'NONE' ] +let s:fg = [ '#c4c4c4', 'NONE' ] +let s:bg1 = [ '#181819', 'NONE' ] +let s:bg2 = [ '#1C1C1D', 'NONE' ] +let s:bg3 = [ '#202022', 'NONE' ] +let s:bg4 = [ '#242426', 'NONE' ] +let s:bg5 = [ '#28282A', 'NONE' ] +let s:color0 = [ '#141415', 'NONE' ] +let s:color1 = [ '#292b37', 'NONE' ] +let s:color2 = [ '#3c2a27', 'NONE' ] +let s:color3 = [ '#3b3033', 'NONE' ] +let s:color4 = [ '#3f3330', 'NONE' ] +let s:color5 = [ '#4b4240', 'NONE' ] +let s:color6 = [ '#66615a', 'NONE' ] +let s:color7 = [ '#c4c4c4', 'NONE' ] +let s:color8 = [ '#4e4e4f', 'NONE' ] +let s:color9 = [ '#292b37', 'NONE' ] +let s:color10 = [ '#3c2a27', 'NONE' ] +let s:color11 = [ '#3b3033', 'NONE' ] +let s:color12 = [ '#3f3330', 'NONE' ] +let s:color13 = [ '#4b4240', 'NONE' ] +let s:color14 = [ '#66615a', 'NONE' ] +let s:color15 = [ '#c4c4c4', 'NONE' ] +let s:color0readable = [ '#c4c4c4', 'NONE' ] +let s:color1readable = [ '#c4c4c4', 'NONE' ] +let s:color2readable = [ '#c4c4c4', 'NONE' ] +let s:color3readable = [ '#c4c4c4', 'NONE' ] +let s:color4readable = [ '#c4c4c4', 'NONE' ] +let s:color5readable = [ '#c4c4c4', 'NONE' ] +let s:color6readable = [ '#c4c4c4', 'NONE' ] +let s:color7readable = [ '#141415', 'NONE' ] +let s:color8readable = [ '#c4c4c4', 'NONE' ] +let s:color9readable = [ '#c4c4c4', 'NONE' ] +let s:color10readable = [ '#c4c4c4', 'NONE' ] +let s:color11readable = [ '#c4c4c4', 'NONE' ] +let s:color12readable = [ '#c4c4c4', 'NONE' ] +let s:color13readable = [ '#c4c4c4', 'NONE' ] +let s:color14readable = [ '#c4c4c4', 'NONE' ] +let s:color15readable = [ '#141415', 'NONE' ] +let s:none = [ 'NONE', 'NONE' ] +let s:test = [ '#ff00ff', 'NONE' ] + +let s:p = {'normal': {}, 'inactive': {}, 'insert': {}, 'replace': {}, 'visual': {}, 'tabline': {}} +let s:p.normal.left = [ [ s:color6readable, s:color6 ], [ s:color2readable, s:color2 ] ] +let s:p.normal.right = [ [ s:color1readable, s:color1 ], [ s:color2readable, s:color2 ] ] +let s:p.inactive.right = [ [ s:color1readable, s:color1 ], [ s:color2readable, s:color2 ] ] +let s:p.inactive.left = [ [ s:color6readable, s:color6 ], [ s:color2readable, s:color2 ] ] +let s:p.insert.left = [ [ s:color5readable, s:color5 ], [ s:color2readable, s:color2 ] ] +let s:p.replace.left = [ [ s:color4readable, s:color4 ], [ s:color2readable, s:color2 ] ] +let s:p.visual.left = [ [ s:color3readable, s:color3 ], [ s:color2readable, s:color2 ] ] +let s:p.normal.middle = [ [ s:none, s:none ] ] +let s:p.inactive.middle = [ [ s:none, s:none ] ] +let s:p.tabline.left = [ [ s:fg, s:bg ] ] +let s:p.tabline.tabsel = [ [ s:fg, s:bg5 ] ] +let s:p.tabline.middle = [ [ s:none, s:none ] ] +let s:p.tabline.right = copy(s:p.normal.right) +let s:p.normal.error = [ [ s:test, s:test ] ] +let s:p.normal.warning = [ [ s:test, s:test ] ] + +let g:lightline#colorscheme#pywal#palette = lightline#colorscheme#flatten(s:p) + diff --git a/.config/picom.conf b/.config/picom.conf new file mode 100644 index 0000000..67afeaa --- /dev/null +++ b/.config/picom.conf @@ -0,0 +1,70 @@ +backend = "glx"; + +fading = false; +fade-delta = 10; +fade-in-step = 0.13; +fade-out-step = 0.1; +shadow = true; +shadow-radius = 48; +shadow-opacity = 0.3; +shadow-offset-y = -48; +shadow-offset-x = -48; + +blur-kern = "3x3box"; +blur-method = "kawase"; +blur-strength = 12; + +blur-background-exclude = [ + "class_g = 'Polybar'", + "class_g = 'firefox' && !I3_FLOATING_WINDOW@:c" +] + +shadow-exclude = [ + "!I3_FLOATING_WINDOW@:c && _NET_WM_WINDOW_TYPE@:32a *= '_NET_WM_WINDOW_TYPE_NORMAL'", + "class_g = 'firefox' && !I3_FLOATING_WINDOW@:c" +] + +opacity-rule = [ + "80:class_g = 'Spotify'", + "90:class_g = 'Zathura'" +] + +wintypes: { + dock = { + shadow = false; + corner-radius = 2; + opacity = 0.999; + }; + + desktop = { + shadow = false; + }; + + tooltip = { + fade = true; + shadow = false; + opacity = 1; + }; + + menu = { + fade = false; + opacity = 0.8; + }; +}; + +# combo +# desktop +# dialog +# dnd +# dock +# dropdown_menu +# menu +# normal +# notify +# popup_menu +# splash +# toolbar +# tooltip +# unknown +# utility + diff --git a/.config/polybar/config.ini b/.config/polybar/config.ini new file mode 100644 index 0000000..a24f161 --- /dev/null +++ b/.config/polybar/config.ini @@ -0,0 +1,358 @@ +[barsettings] +padding = 1.9 + +[global/wm] +margin-bottom = 50 +margin-top = 0 + +[bar/main] + +; tray-position = right +; tray-padding = ${barsettings.padding} +; tray-background = ${color.bg} +; tray-margin = 0 + +monitor = + +monitor-fallback = + +monitor-strict = false + +override-redirect = false + +bottom = false + +fixed-center = true + +width = 100% +height = 16 + +offset-x = 0 +offset-y = 0 + +background = ${color.alpha} + +foreground = ${color.fg} + +radius-top = 0.0 +radius-bottom = 0.0 + +padding = 0 + +module-margin-left = 0 +module-margin-right = 0 + +font-0 = "Scientifica:weight=regular:size=4;2" +font-1 = "Weather Icons:antialias=false:size=6;2" +font-2 = "Siji:size=8;2" +font-3 = "Material Design Icons:antialias=false:size=8;2" +font-4 = "Fira Code:size=10;2" + +modules-center = onstart time weather date alsa + +separator = + +spacing = 0 + +dim-value = 1.0 + +wm-name = + +locale = + +wm-restack = i3 + +enable-ipc = true + +click-left = +click-middle = +click-right = +scroll-up = +scroll-down = +double-click-left = +double-click-middle = +double-click-right = + +cursor-click = +cursor-scroll = + +[settings] +throttle-output = 5 +throttle-output-for = 10 + +throttle-input-for = 30 + +screenchange-reload = false + +compositing-background = source +compositing-foreground = over +compositing-overline = over +compositing-underline = over +compositing-border = over + +format-foreground = +format-background = +format-underline = +format-overline = +format-spacing = +format-padding = +format-margin = +format-offset = + +pseudo-transparency = false + +[color] + +bg = #141415 +fg = #c4c4c4 +fg-alt = #141415 + +alpha = #00000000 + +shade1 = #3F3330 +shade2 = #4C423E +shade3 = #59514C +shade4 = #66615A + +shade-fg = #c4c4c4 + +[module/alsa] +type = internal/alsa + +format-volume = +format-volume-background = ${color.shade4} +format-volume-foreground = ${color.shade-fg} +format-volume-padding = ${barsettings.padding} + +label-volume = %percentage%% + +format-muted-prefix = " " +format-muted-background = ${color.shade-fg} +format-muted-foreground = ${color.shade4} +format-muted-padding = ${barsettings.padding} +label-muted = "Muted" + +ramp-volume-0 =  +ramp-volume-1 =  +ramp-volume-2 =  + +ramp-headphones-0 =  +ramp-headphones-1 =  + +[module/battery] +type = internal/battery + +full-at = 99 +battery = BAT1 +adapter = ACAD +poll-interval = 2 +time-format = %H:%M + +format-charging = +format-charging-background = ${color.shade3} +format-charging-foreground = ${color.fg-alt} +format-charging-padding = ${barsettings.padding} + +format-discharging = +format-discharging-background = ${color.shade3} +format-discharging-foreground = ${color.fg-alt} +format-discharging-padding = ${barsettings.padding} + +format-full = +format-full-background = ${color.shade3} +format-full-foreground = ${color.fg-alt} +format-full-padding = ${barsettings.padding} + +label-charging = %percentage%% +label-discharging = %percentage%% +label-full = 100% Charged + +ramp-capacity-0 =  +ramp-capacity-1 =  +ramp-capacity-2 =  +ramp-capacity-3 =  +ramp-capacity-4 =  +ramp-capacity-5 =  +ramp-capacity-6 =  +ramp-capacity-7 =  +ramp-capacity-8 =  +ramp-capacity-9 =  + +animation-charging-0 =  +animation-charging-1 =  +animation-charging-2 =  +animation-charging-3 =  + +animation-charging-framerate = 750 + +[module/cpu] +type = internal/cpu + +interval = 1 + +format =