Expand / minimize custom tags; better character image view; better guestbook view; mouseover preview on character profile links
This commit is contained in:
		
							parent
							
								
									af1960ed02
								
							
						
					
					
						commit
						7bdf9d815a
					
				@ -72,6 +72,7 @@ export class CoreBBCodeParser extends BBCodeParser {
 | 
			
		||||
            parent.appendChild(el);
 | 
			
		||||
            return el;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        this.addTag(new BBCodeTextTag('url', (parser, parent, param, content) => {
 | 
			
		||||
            const tagData = analyzeUrlTag(parser, param, content);
 | 
			
		||||
            const element = parser.createElement('span');
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,11 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { BBCodeElement } from '../chat/bbcode';
 | 
			
		||||
import {InlineImage} from '../interfaces';
 | 
			
		||||
import {CoreBBCodeParser} from './core';
 | 
			
		||||
import { analyzeUrlTag, CoreBBCodeParser } from './core';
 | 
			
		||||
import {InlineDisplayMode} from './interfaces';
 | 
			
		||||
import {BBCodeCustomTag, BBCodeSimpleTag, BBCodeTextTag} from './parser';
 | 
			
		||||
import UrlTagView from '../chat/UrlTagView.vue';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface StandardParserSettings {
 | 
			
		||||
    siteDomain: string
 | 
			
		||||
@ -16,6 +20,8 @@ export class StandardBBCodeParser extends CoreBBCodeParser {
 | 
			
		||||
    allowInlines = true;
 | 
			
		||||
    inlines: {[key: string]: InlineImage | undefined} | undefined;
 | 
			
		||||
 | 
			
		||||
    cleanup: Vue[] = [];
 | 
			
		||||
 | 
			
		||||
    createInline(inline: InlineImage): HTMLElement {
 | 
			
		||||
        const p1 = inline.hash.substr(0, 2);
 | 
			
		||||
        const p2 = inline.hash.substr(2, 2);
 | 
			
		||||
@ -187,6 +193,38 @@ export class StandardBBCodeParser extends CoreBBCodeParser {
 | 
			
		||||
            } else parent.appendChild(element = parser.createInline(inline));
 | 
			
		||||
            return element;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        this.addTag(new BBCodeTextTag(
 | 
			
		||||
            'url',
 | 
			
		||||
            (parser, parent, _, content) => {
 | 
			
		||||
                const tagData = analyzeUrlTag(parser, _, content);
 | 
			
		||||
 | 
			
		||||
                const root = parser.createElement('span');
 | 
			
		||||
                // const el = parser.createElement('span');
 | 
			
		||||
                parent.appendChild(root);
 | 
			
		||||
                // root.appendChild(el);
 | 
			
		||||
 | 
			
		||||
                if (!tagData.success) {
 | 
			
		||||
                    root.textContent = tagData.textContent;
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const view = new UrlTagView({el: root, propsData: {url: tagData.url, text: tagData.textContent, domain: tagData.domain}});
 | 
			
		||||
                this.cleanup.push(view);
 | 
			
		||||
 | 
			
		||||
                return root;
 | 
			
		||||
            }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    parseEverything(input: string): BBCodeElement {
 | 
			
		||||
        const elm = <BBCodeElement>super.parseEverything(input);
 | 
			
		||||
        if(this.cleanup.length > 0)
 | 
			
		||||
            elm.cleanup = ((cleanup: Vue[]) => () => {
 | 
			
		||||
                for(const component of cleanup) component.$destroy();
 | 
			
		||||
            })(this.cleanup);
 | 
			
		||||
        this.cleanup = [];
 | 
			
		||||
        return elm;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -90,6 +90,7 @@
 | 
			
		||||
        <report-dialog ref="reportDialog"></report-dialog>
 | 
			
		||||
        <user-menu ref="userMenu" :reportDialog="$refs['reportDialog']"></user-menu>
 | 
			
		||||
        <recent-conversations ref="recentDialog"></recent-conversations>
 | 
			
		||||
        <image-preview ref="imagePreview"></image-preview>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@ -114,6 +115,7 @@
 | 
			
		||||
    import {getStatusIcon} from './user_view';
 | 
			
		||||
    import UserList from './UserList.vue';
 | 
			
		||||
    import UserMenu from './UserMenu.vue';
 | 
			
		||||
    import ImagePreview from './ImagePreview.vue';
 | 
			
		||||
 | 
			
		||||
    const unreadClasses = {
 | 
			
		||||
        [Conversation.UnreadState.None]: '',
 | 
			
		||||
@ -125,7 +127,8 @@
 | 
			
		||||
        components: {
 | 
			
		||||
            'user-list': UserList, channels: ChannelList, 'status-switcher': StatusSwitcher, 'character-search': CharacterSearch,
 | 
			
		||||
            settings: SettingsView, conversation: ConversationView, 'report-dialog': ReportDialog, sidebar: Sidebar,
 | 
			
		||||
            'user-menu': UserMenu, 'recent-conversations': RecentConversations
 | 
			
		||||
            'user-menu': UserMenu, 'recent-conversations': RecentConversations,
 | 
			
		||||
            'image-preview': ImagePreview
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    export default class ChatView extends Vue {
 | 
			
		||||
 | 
			
		||||
@ -136,7 +136,6 @@
 | 
			
		||||
        <settings ref="settingsDialog" :conversation="conversation"></settings>
 | 
			
		||||
        <logs ref="logsDialog" :conversation="conversation"></logs>
 | 
			
		||||
        <manage-channel ref="manageDialog" v-if="isChannel(conversation)" :channel="conversation.channel"></manage-channel>
 | 
			
		||||
        <image-preview ref="imagePreview"></image-preview>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@ -151,7 +150,6 @@
 | 
			
		||||
    import { characterImage, getByteLength, getKey } from "./common";
 | 
			
		||||
    import ConversationSettings from './ConversationSettings.vue';
 | 
			
		||||
    import core from './core';
 | 
			
		||||
    import ImagePreview from './ImagePreview.vue';
 | 
			
		||||
    import {Channel, channelModes, Character, Conversation, Settings} from './interfaces';
 | 
			
		||||
    import l from './localize';
 | 
			
		||||
    import Logs from './Logs.vue';
 | 
			
		||||
@ -164,8 +162,7 @@
 | 
			
		||||
    @Component({
 | 
			
		||||
        components: {
 | 
			
		||||
            user: UserView, 'bbcode-editor': Editor, 'manage-channel': ManageChannel, settings: ConversationSettings,
 | 
			
		||||
            logs: Logs, 'message-view': MessageView, bbcode: BBCodeView, 'command-help': CommandHelp,
 | 
			
		||||
            'image-preview': ImagePreview
 | 
			
		||||
            logs: Logs, 'message-view': MessageView, bbcode: BBCodeView, 'command-help': CommandHelp
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    export default class ConversationView extends Vue {
 | 
			
		||||
 | 
			
		||||
@ -267,4 +267,8 @@
 | 
			
		||||
        background-repeat: no-repeat;
 | 
			
		||||
        // background-color: black;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .image-preview-wrapper {
 | 
			
		||||
        z-index: 10000;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,10 @@ export class ImagePreviewMutator {
 | 
			
		||||
        this.add('e-hentai.org', this.getBaseJsMutatorScript('#img, video'));
 | 
			
		||||
        this.add('gelbooru.com', this.getBaseJsMutatorScript('#image, video'));
 | 
			
		||||
        this.add('chan.sankakucomplex.com', this.getBaseJsMutatorScript('#image, video'));
 | 
			
		||||
        this.add('danbooru.donmai.us', this.getBaseJsMutatorScript('#image, video'));
 | 
			
		||||
        this.add('gfycat.com', this.getBaseJsMutatorScript('video'));
 | 
			
		||||
        this.add('gfycatporn.com', this.getBaseJsMutatorScript('video'));
 | 
			
		||||
        this.add('www.youtube.com', this.getBaseJsMutatorScript('video'));
 | 
			
		||||
 | 
			
		||||
        // this fixes videos only -- images are fine as is
 | 
			
		||||
        this.add('i.imgur.com', this.getBaseJsMutatorScript('video'));
 | 
			
		||||
@ -46,9 +49,9 @@ export class ImagePreviewMutator {
 | 
			
		||||
        this.add(
 | 
			
		||||
            'imgur.com',
 | 
			
		||||
            `
 | 
			
		||||
                const imageCount = $('.post-image-container').length;
 | 
			
		||||
                const imageCount = $('.post-container video, .post-container img').length;
 | 
			
		||||
 | 
			
		||||
                ${this.getBaseJsMutatorScript('.image.post-image img, .image.post-image video')}
 | 
			
		||||
                ${this.getBaseJsMutatorScript('.post-container video, .post-container img', true)}
 | 
			
		||||
 | 
			
		||||
                if(imageCount > 1)
 | 
			
		||||
                    $('#flistWrapper').append('<div id="imageCount" style="position: absolute; bottom: 0; right: 0; background: green; border: 2px solid lightgreen; width: 5rem; height: 5rem; font-size: 2rem; font-weight: bold; color: white; border-radius: 5rem; margin: 0.75rem;"><div style="position: absolute; top: 50%; left: 50%; transform: translateY(-50%) translateX(-50%);">+' + (imageCount - 1) + '</div></div>');
 | 
			
		||||
@ -65,28 +68,41 @@ export class ImagePreviewMutator {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getBaseJsMutatorScript(imageSelector: string): string {
 | 
			
		||||
    getBaseJsMutatorScript(imageSelector: string, skipElementRemove = false): string {
 | 
			
		||||
        return `const body = document.querySelector('body');
 | 
			
		||||
            const img = document.querySelector('${imageSelector}');
 | 
			
		||||
            const el = document.createElement('div');
 | 
			
		||||
            el.id = 'flistWrapper';
 | 
			
		||||
 | 
			
		||||
            el.style = 'width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 100000; '
 | 
			
		||||
                + 'background-color: black; background-size: contain; background-repeat: no-repeat; background-position: top left;';
 | 
			
		||||
 | 
			
		||||
            img.remove();
 | 
			
		||||
            el.append(img);
 | 
			
		||||
            body.append(el);
 | 
			
		||||
            body.style = 'padding: 0; margin: 0; overflow: hidden; width: 100%; height: 100%';
 | 
			
		||||
            img.style = 'object-position: top left; object-fit: contain; width: 100%; height: 100%;'
 | 
			
		||||
            
 | 
			
		||||
            if (img.play) { img.muted = true; img.play(); }
 | 
			
		||||
            
 | 
			
		||||
            let removeList = [];
 | 
			
		||||
            body.childNodes.forEach((el) => { if(el.id !== 'flistWrapper') { removeList.push(el); } });
 | 
			
		||||
            removeList.forEach((el) => el.remove());
 | 
			
		||||
            removeList = [];
 | 
			
		||||
            
 | 
			
		||||
        const img = document.querySelector('${imageSelector}');
 | 
			
		||||
        const el = document.createElement('div');
 | 
			
		||||
        el.id = 'flistWrapper';
 | 
			
		||||
        
 | 
			
		||||
        el.style = 'width: 100% !important; height: 100% !important; position: absolute !important;'
 | 
			
		||||
            + 'top: 0 !important; left: 0 !important; z-index: 100000 !important;'
 | 
			
		||||
            + 'background-color: black !important; background-size: contain !important;'
 | 
			
		||||
            + 'background-repeat: no-repeat !important; background-position: top left !important;'
 | 
			
		||||
            + 'opacity: 1 !important; padding: 0 !important; border: 0 !important; margin: 0 !important;';
 | 
			
		||||
        
 | 
			
		||||
        img.remove();
 | 
			
		||||
        el.append(img);
 | 
			
		||||
        body.append(el);
 | 
			
		||||
        body.class = '';
 | 
			
		||||
        
 | 
			
		||||
        body.style = 'border: 0 !important; padding: 0 !important; margin: 0 !important; overflow: hidden !important;'
 | 
			
		||||
            + 'width: 100% !important; height: 100% !important; opacity: 1 !important;'
 | 
			
		||||
            + 'top: 0 !important; left: 0 !important;';
 | 
			
		||||
        
 | 
			
		||||
        img.style = 'object-position: top left !important; object-fit: contain !important;'
 | 
			
		||||
            + 'width: 100% !important; height: 100% !important; opacity: 1 !important;'
 | 
			
		||||
            + 'margin: 0 !imporant; border: 0 !important; padding: 0 !important;';
 | 
			
		||||
        
 | 
			
		||||
        img.class = '';
 | 
			
		||||
        el.class = '';
 | 
			
		||||
        
 | 
			
		||||
        if (img.play) { img.muted = true; img.play(); }
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        let removeList = [];
 | 
			
		||||
        body.childNodes.forEach((el) => { if(el.id !== 'flistWrapper') { removeList.push(el); } });
 | 
			
		||||
        ${skipElementRemove ? '' : 'removeList.forEach((el) => el.remove());'}
 | 
			
		||||
        removeList = [];
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,12 +2,13 @@ import Axios from 'axios';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Editor from '../bbcode/Editor.vue';
 | 
			
		||||
import {InlineDisplayMode} from '../bbcode/interfaces';
 | 
			
		||||
import {initParser, standardParser} from '../bbcode/standard';
 | 
			
		||||
import { initParser, standardParser } from '../bbcode/standard';
 | 
			
		||||
import CharacterLink from '../components/character_link.vue';
 | 
			
		||||
import CharacterSelect from '../components/character_select.vue';
 | 
			
		||||
import {setCharacters} from '../components/character_select/character_list';
 | 
			
		||||
import DateDisplay from '../components/date_display.vue';
 | 
			
		||||
import SimplePager from '../components/simple_pager.vue';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    Character as CharacterInfo, CharacterImage, CharacterImageOld, CharacterInfotag, CharacterSettings, KinkChoice
 | 
			
		||||
} from '../interfaces';
 | 
			
		||||
@ -180,6 +181,7 @@ async function kinksGet(id: number): Promise<CharacterKink[]> {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export function init(characters: {[key: string]: number}): void {
 | 
			
		||||
    Utils.setDomains(parserSettings.siteDomain, parserSettings.staticDomain);
 | 
			
		||||
    initParser(parserSettings);
 | 
			
		||||
 | 
			
		||||
@ -152,11 +152,11 @@
 | 
			
		||||
                    parent.send('switch-tab', this.character);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
            /*if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
                const dt = require('@vue/devtools');
 | 
			
		||||
 | 
			
		||||
                dt.connect();
 | 
			
		||||
            }
 | 
			
		||||
            }*/
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										121
									
								
								electron/matcher.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								electron/matcher.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,121 @@
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
export enum TagId {
 | 
			
		||||
    Age = 1,
 | 
			
		||||
    Orientation = 2,
 | 
			
		||||
    Gender = 3,
 | 
			
		||||
    Build = 13,
 | 
			
		||||
    FurryPreference = 49,
 | 
			
		||||
    BdsmRole = 15,
 | 
			
		||||
    Position = 41,
 | 
			
		||||
    BodyType = 51,
 | 
			
		||||
    ApparentAge = 64,
 | 
			
		||||
    RelationshipStatus = 42,
 | 
			
		||||
    Species = 9,
 | 
			
		||||
    LanguagePreference = 49
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export enum Orientation {
 | 
			
		||||
    Straight = 4,
 | 
			
		||||
    Gay = 5,
 | 
			
		||||
    Bisexual = 6,
 | 
			
		||||
    Asexual = 7,
 | 
			
		||||
    Unsure = 8,
 | 
			
		||||
    BiMalePreference = 89,
 | 
			
		||||
    BiFemalePreference = 90,
 | 
			
		||||
    Pansexual = 127,
 | 
			
		||||
    BiCurious = 128
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
orientationCompatibilityMap[Orientation.Straight] = [
 | 
			
		||||
    [Orientation.Straight, 1],
 | 
			
		||||
    [Orientation.Gay, -1],
 | 
			
		||||
    [Orientation.Bisexual, 1],
 | 
			
		||||
    [Orientation.Asexual, 0],
 | 
			
		||||
    [Orientation.Unsure, 0],
 | 
			
		||||
    [Orientation.BiMalePreference, (c: CharacterInfo) => (isMale(c) ? 1 : 0.5)],
 | 
			
		||||
    [Orientation.BiFemalePreference, (c: CharacterInfo) => (isFemale(c) ? 1 : 0.5)],
 | 
			
		||||
    [Orientation.Pansexual, 1],
 | 
			
		||||
    [Orientation.BiCurious, 0]
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
orientationCompatibilityMap[Orientation.Gay] = [
 | 
			
		||||
    [Orientation.Straight, -1],
 | 
			
		||||
    [Orientation.Gay, 1],
 | 
			
		||||
    [Orientation.Bisexual, 1],
 | 
			
		||||
    [Orientation.Asexual, 0],
 | 
			
		||||
    [Orientation.Unsure, 0],
 | 
			
		||||
    [Orientation.BiMalePreference, (c: CharacterInfo) => (isMale(c) ? 1 : 0.5)],
 | 
			
		||||
    [Orientation.BiFemalePreference, (c: CharacterInfo) => (isFemale(c) ? 1 : 0.5)],
 | 
			
		||||
    [Orientation.Pansexual, 1],
 | 
			
		||||
    [Orientation.BiCurious, (c: CharacterInfo, t) => isSameGender(c, t) ? 0.5 : 1]
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
orientationCompatibilityMap[Orientation.Bisexual] = [
 | 
			
		||||
    [Orientation.Straight, 1],
 | 
			
		||||
    [Orientation.Gay, 1],
 | 
			
		||||
    [Orientation.Bisexual, 1],
 | 
			
		||||
    [Orientation.Asexual, 0],
 | 
			
		||||
    [Orientation.Unsure, 0],
 | 
			
		||||
    [Orientation.BiMalePreference, (c: CharacterInfo) => (isMale(c) ? 1 : 0.5)],
 | 
			
		||||
    [Orientation.BiFemalePreference, (c: CharacterInfo) => (isFemale(c) ? 1 : 0.5)],
 | 
			
		||||
    [Orientation.Pansexual, 1],
 | 
			
		||||
    [Orientation.BiCurious, 0]
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
orientationCompatibilityMap[Orientation.Asexual] = [];
 | 
			
		||||
orientationCompatibilityMap[Orientation.Unsure] = [];
 | 
			
		||||
 | 
			
		||||
orientationCompatibilityMap[Orientation.BiMalePreference] = [
 | 
			
		||||
    [Orientation.Straight, -1],
 | 
			
		||||
    [Orientation.Gay, 1],
 | 
			
		||||
    [Orientation.Bisexual, 1],
 | 
			
		||||
    [Orientation.Asexual, 0],
 | 
			
		||||
    [Orientation.Unsure, 0],
 | 
			
		||||
    [Orientation.BiMalePreference, (c: CharacterInfo) => (isMale(c) ? 1 : 0.5)],
 | 
			
		||||
    [Orientation.BiFemalePreference, (c: CharacterInfo) => (isFemale(c) ? 1 : 0.5)],
 | 
			
		||||
    [Orientation.Pansexual, 1],
 | 
			
		||||
    [Orientation.BiCurious, 0]
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class Matcher {
 | 
			
		||||
 | 
			
		||||
    static readonly TAGID_AGE = 1;
 | 
			
		||||
 | 
			
		||||
    static readonly TAGID_ORIENTATION = 2;
 | 
			
		||||
 | 
			
		||||
    static readonly TAGID_GENDER = 3;
 | 
			
		||||
 | 
			
		||||
    static readonly TAGID_FURRY_PREFERENCE = 49;
 | 
			
		||||
 | 
			
		||||
    static readonly TAGID_BUILD = 13;
 | 
			
		||||
 | 
			
		||||
    static readonly TAGID_BDSM_ROLE = 15;
 | 
			
		||||
    static readonly TAGID_POSITION = 41;
 | 
			
		||||
 | 
			
		||||
    static readonly TAGID_BODY_TYPE = 51;
 | 
			
		||||
    static readonly TAGID_APPARENT_AGE = 64;
 | 
			
		||||
 | 
			
		||||
    static readonly TAGID_RELATIONSHIP = 42;
 | 
			
		||||
 | 
			
		||||
    static readonly TAGID_SPECIES = 9;
 | 
			
		||||
    static readonly TAGID_LANGUAGE_PREFERENCE = 49;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -132,3 +132,16 @@ div.indentText {
 | 
			
		||||
.user-link {
 | 
			
		||||
  text-shadow: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.bbcode {
 | 
			
		||||
  .fa-link {
 | 
			
		||||
      color: white;
 | 
			
		||||
      margin-right: 2px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  a.user-link {
 | 
			
		||||
      color: white;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@
 | 
			
		||||
            <div class="alert alert-danger" v-show="error">{{error}}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-4 col-lg-3 col-xl-2" v-if="!loading && character">
 | 
			
		||||
            <sidebar :character="character" @memo="memo" @bookmarked="bookmarked" :oldApi="oldApi"></sidebar>
 | 
			
		||||
            <sidebar :character="character" :selfCharacter="selfCharacter" @memo="memo" @bookmarked="bookmarked" :oldApi="oldApi"></sidebar>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-8 col-lg-9 col-xl-10 profile-body" v-if="!loading && character">
 | 
			
		||||
            <div id="characterView">
 | 
			
		||||
@ -82,6 +82,7 @@
 | 
			
		||||
    import InfotagsView from './infotags.vue';
 | 
			
		||||
    import CharacterKinksView from './kinks.vue';
 | 
			
		||||
    import Sidebar from './sidebar.vue';
 | 
			
		||||
    import core from '../../chat/core';
 | 
			
		||||
 | 
			
		||||
    interface ShowableVueTab extends Vue {
 | 
			
		||||
        show?(): void
 | 
			
		||||
@ -116,6 +117,8 @@
 | 
			
		||||
        error = '';
 | 
			
		||||
        tab = '0';
 | 
			
		||||
 | 
			
		||||
        selfCharacter: Character | undefined;
 | 
			
		||||
 | 
			
		||||
        @Hook('beforeMount')
 | 
			
		||||
        beforeMount(): void {
 | 
			
		||||
            this.shared.authenticated = this.authenticated;
 | 
			
		||||
@ -123,7 +126,7 @@
 | 
			
		||||
 | 
			
		||||
        @Hook('mounted')
 | 
			
		||||
        async mounted(): Promise<void> {
 | 
			
		||||
            if(this.character === undefined) await this._getCharacter();
 | 
			
		||||
            await this.load(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Watch('tab')
 | 
			
		||||
@ -136,9 +139,34 @@
 | 
			
		||||
        @Watch('name')
 | 
			
		||||
        async onCharacterSet(): Promise<void> {
 | 
			
		||||
            this.tab = '0';
 | 
			
		||||
            return this._getCharacter();
 | 
			
		||||
            return this.load();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        async load(mustLoad = true) {
 | 
			
		||||
            this.loading = true;
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                const due: Promise<any>[] = [];
 | 
			
		||||
 | 
			
		||||
                if ((this.selfCharacter === undefined) && (Utils.Settings.defaultCharacter >= 0)) {
 | 
			
		||||
                    due.push(this.loadSelfCharacter());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if((mustLoad === true) || (this.character === undefined)) {
 | 
			
		||||
                    due.push(this._getCharacter());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await Promise.all(due);
 | 
			
		||||
            } catch(e) {
 | 
			
		||||
                this.error = Utils.isJSONError(e) ? <string>e.response.data.error : (<Error>e).message;
 | 
			
		||||
                Utils.ajaxError(e, 'Failed to load character information.');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.loading = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        memo(memo: {id: number, memo: string}): void {
 | 
			
		||||
            Vue.set(this.character!, 'memo', memo);
 | 
			
		||||
        }
 | 
			
		||||
@ -147,29 +175,73 @@
 | 
			
		||||
            Vue.set(this.character!, 'bookmarked', state);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        protected async loadSelfCharacter(): Promise<Character> {
 | 
			
		||||
            console.log('SELF');
 | 
			
		||||
 | 
			
		||||
            const ownChar = core.characters.ownCharacter;
 | 
			
		||||
 | 
			
		||||
            this.selfCharacter = await methods.characterData(ownChar.name, -1);
 | 
			
		||||
 | 
			
		||||
            console.log('SELF LOADED');
 | 
			
		||||
 | 
			
		||||
            return this.selfCharacter;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        private async _getCharacter(): Promise<void> {
 | 
			
		||||
            this.error = '';
 | 
			
		||||
            this.character = undefined;
 | 
			
		||||
            if(this.name === undefined || this.name.length === 0)
 | 
			
		||||
                return;
 | 
			
		||||
            try {
 | 
			
		||||
                this.loading = true;
 | 
			
		||||
                await methods.fieldsGet();
 | 
			
		||||
                this.character = await methods.characterData(this.name, this.characterid);
 | 
			
		||||
                standardParser.allowInlines = true;
 | 
			
		||||
                standardParser.inlines = this.character.character.inlines;
 | 
			
		||||
 | 
			
		||||
            } catch(e) {
 | 
			
		||||
                this.error = Utils.isJSONError(e) ? <string>e.response.data.error : (<Error>e).message;
 | 
			
		||||
                Utils.ajaxError(e, 'Failed to load character information.');
 | 
			
		||||
            }
 | 
			
		||||
            this.loading = false;
 | 
			
		||||
            await methods.fieldsGet();
 | 
			
		||||
            this.character = await methods.characterData(this.name, this.characterid);
 | 
			
		||||
            standardParser.allowInlines = true;
 | 
			
		||||
            standardParser.inlines = this.character.character.inlines;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
    .compare-highlight-block {
 | 
			
		||||
        margin-bottom: 3px;
 | 
			
		||||
 | 
			
		||||
        .quick-compare-block button {
 | 
			
		||||
            margin-left: 2px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .character-kinks-block {
 | 
			
		||||
        i.fa {
 | 
			
		||||
            margin-right: 0.25rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        .character-kink {
 | 
			
		||||
            .popover {
 | 
			
		||||
                min-width: 200px;
 | 
			
		||||
                margin-bottom: 0;
 | 
			
		||||
                padding-bottom: 0;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            p {
 | 
			
		||||
                line-height: 125%;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            p:last-child {
 | 
			
		||||
                margin-bottom:0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .expanded-custom-kink {
 | 
			
		||||
        .custom-kink {
 | 
			
		||||
            margin-top: 14px;
 | 
			
		||||
            margin-bottom: 14px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .custom-kink {
 | 
			
		||||
        &:first-child {
 | 
			
		||||
@ -180,12 +252,19 @@
 | 
			
		||||
            margin-bottom: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        margin-top: 14px;
 | 
			
		||||
        margin-bottom: 14px;
 | 
			
		||||
        .kink-name {
 | 
			
		||||
            font-weight: bold;
 | 
			
		||||
            color: #f2cd00;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        i {
 | 
			
		||||
            color: #f2cd00;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        margin-top: 7px;
 | 
			
		||||
        margin-bottom: 7px;
 | 
			
		||||
        margin-left: -6px;
 | 
			
		||||
        margin-right: -6px;
 | 
			
		||||
        color: #f2cd00;
 | 
			
		||||
        border: 1px rgba(255, 255, 255, 0.1) solid;
 | 
			
		||||
        border-radius: 2px;
 | 
			
		||||
        /* border-collapse: collapse; */
 | 
			
		||||
@ -193,6 +272,14 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    .stock-kink {
 | 
			
		||||
        .kink-name, i {
 | 
			
		||||
            color: #ededf6;
 | 
			
		||||
            font-weight: normal;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    .kink-custom-desc {
 | 
			
		||||
        display: block;
 | 
			
		||||
        font-weight: normal;
 | 
			
		||||
@ -239,13 +326,98 @@
 | 
			
		||||
        color: rgba(255, 255, 255, 0.7);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    img.character-image {
 | 
			
		||||
        max-width: 33% !important;
 | 
			
		||||
        width: 33% !important;
 | 
			
		||||
        height: auto !important;
 | 
			
		||||
        object-fit: contain;
 | 
			
		||||
        object-position: top center;
 | 
			
		||||
        vertical-align: top !important;
 | 
			
		||||
    .guestbook-post {
 | 
			
		||||
        margin-bottom: 15px;
 | 
			
		||||
        margin-top: 15px;
 | 
			
		||||
        background-color: rgba(0,0,0,0.15);
 | 
			
		||||
        border-radius: 5px;
 | 
			
		||||
        padding: 15px;
 | 
			
		||||
        border: 1px solid rgba(255, 255, 255, 0.1);
 | 
			
		||||
 | 
			
		||||
        .characterLink {
 | 
			
		||||
            font-size: 20pt;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .guestbook-timestamp {
 | 
			
		||||
            color: rgba(255, 255, 255, 0.3);
 | 
			
		||||
            font-size: 85%
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .guestbook-message {
 | 
			
		||||
            margin-top: 10px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .guestbook-reply {
 | 
			
		||||
            margin-top: 20px;
 | 
			
		||||
            background-color: rgba(0,0,0, 0.1);
 | 
			
		||||
            padding: 15px;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    .contact-block {
 | 
			
		||||
        margin-top: 25px !important;
 | 
			
		||||
        margin-bottom: 25px !important;
 | 
			
		||||
 | 
			
		||||
        .contact-method {
 | 
			
		||||
            font-size: 80%;
 | 
			
		||||
            display: block;
 | 
			
		||||
            margin-bottom: 2px;
 | 
			
		||||
 | 
			
		||||
            img {
 | 
			
		||||
                border-radius: 2px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #character-page-sidebar .character-list-block {
 | 
			
		||||
        .character-avatar.icon {
 | 
			
		||||
            height: 43px !important;
 | 
			
		||||
            width: 43px !important;
 | 
			
		||||
            border-radius: 3px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        .characterLink {
 | 
			
		||||
            font-size: 85%;
 | 
			
		||||
            padding-left: 3px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .character-images {
 | 
			
		||||
        .character-image-wrapper {
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            background-color: rgba(0,0,0, 0.2);
 | 
			
		||||
            border-radius: 5px;
 | 
			
		||||
            width: calc(50% - 20px);
 | 
			
		||||
            box-sizing: border-box;
 | 
			
		||||
            margin: 5px;
 | 
			
		||||
            // float: left;
 | 
			
		||||
            /* margin-bottom: auto; */
 | 
			
		||||
            /* margin-top: auto; */
 | 
			
		||||
 | 
			
		||||
            a {
 | 
			
		||||
                border: none;
 | 
			
		||||
 | 
			
		||||
                img {
 | 
			
		||||
                    max-width: 100% !important;
 | 
			
		||||
                    width: 100% !important;
 | 
			
		||||
                    height:  auto !important;
 | 
			
		||||
                    object-fit:  contain;
 | 
			
		||||
                    object-position: top center;
 | 
			
		||||
                    vertical-align: top !important;
 | 
			
		||||
                    border-radius: 6px;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .image-description {
 | 
			
		||||
                font-size: 85%;
 | 
			
		||||
                padding-top: 5px;
 | 
			
		||||
                padding-bottom: 5px;
 | 
			
		||||
                padding-left: 10px;
 | 
			
		||||
                padding-right: 10px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
@ -12,7 +12,7 @@
 | 
			
		||||
                    <span v-show="!post.approved" class="post-unapproved"> (unapproved)</span>
 | 
			
		||||
 | 
			
		||||
                    <span class="guestbook-timestamp">
 | 
			
		||||
                        <character-link :character="post.character"></character-link>, posted <date-display
 | 
			
		||||
                        <character-link :character="post.character"></character-link> posted <date-display
 | 
			
		||||
                        :time="post.postedAt"></date-display>
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <button class="btn btn-secondary" v-show="canEdit" @click="approve" :disabled="approving" style="margin-left:10px">
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,13 @@
 | 
			
		||||
    <div class="character-images">
 | 
			
		||||
        <div v-show="loading" class="alert alert-info">Loading images.</div>
 | 
			
		||||
        <template v-if="!loading">
 | 
			
		||||
            <img :src="imageUrl(image)" :title="image.description" class="character-image" v-for="image in images" :key="image.id">
 | 
			
		||||
            <!-- @click="handleImageClick($event, image)" -->
 | 
			
		||||
            <div v-for="image in images" :key="image.id" class="character-image-wrapper">
 | 
			
		||||
                <a :href="imageUrl(image)" target="_blank">
 | 
			
		||||
                    <img :src="imageUrl(image)" class="character-image">
 | 
			
		||||
                </a>
 | 
			
		||||
                <div class="image-description" v-if="!!image.description">{{image.description}}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
<!--            <div class="character-image col-6 col-sm-12 col-md-12" v-for="image in images" :key="image.id">-->
 | 
			
		||||
<!--                <img :src="imageUrl(image)" :title="image.description">-->
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="infotag">
 | 
			
		||||
    <div :class="tagClasses">
 | 
			
		||||
        <span class="infotag-label">{{label}}</span>
 | 
			
		||||
        <span v-if="!contactLink" class="infotag-value">{{value}}</span>
 | 
			
		||||
        <span v-if="contactLink" class="infotag-value"><a :href="contactLink">{{value}}</a></span>
 | 
			
		||||
@ -10,14 +10,55 @@
 | 
			
		||||
    import {Component, Prop} from '@f-list/vue-ts';
 | 
			
		||||
    import Vue from 'vue';
 | 
			
		||||
    import {formatContactLink, formatContactValue} from './contact_utils';
 | 
			
		||||
    import { Character, DisplayInfotag } from './interfaces';
 | 
			
		||||
    // import { Character as CharacterInfo } from '../../interfaces';
 | 
			
		||||
    import {Store} from './data_store';
 | 
			
		||||
    import {DisplayInfotag} from './interfaces';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Component
 | 
			
		||||
    export default class InfotagView extends Vue {
 | 
			
		||||
        @Prop({required: true})
 | 
			
		||||
        private readonly infotag!: DisplayInfotag;
 | 
			
		||||
 | 
			
		||||
        @Prop({required: true})
 | 
			
		||||
        private readonly selfCharacter!: Character;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        get tagClasses() {
 | 
			
		||||
            const styles = {
 | 
			
		||||
                infotag: true,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            //console.log('TAG', this.label, this.value, this.infotag);
 | 
			
		||||
 | 
			
		||||
            return this.getCharacterCompatibilityStyles(styles, this.selfCharacter);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        getCharacterCompatibilityStyles(styles: any, a: Character) {
 | 
			
		||||
            if (a.character.name) {
 | 
			
		||||
                styles.infotag = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // const c: CharacterInfo = this.selfCharacter.character;
 | 
			
		||||
            // const t = this.infotag;
 | 
			
		||||
 | 
			
		||||
           /* console.log(this.label, this.value, this.infotag.id, this.infotag, c.infotags);
 | 
			
		||||
 | 
			
		||||
            switch (t.id) {
 | 
			
		||||
                case InfotagView.TAGID_ORIENTATION:
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                    // do nothing;
 | 
			
		||||
                    break;
 | 
			
		||||
            }*/
 | 
			
		||||
 | 
			
		||||
            return styles;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        get label(): string {
 | 
			
		||||
            const infotag = Store.kinks.infotags[this.infotag.id];
 | 
			
		||||
            if(typeof infotag === 'undefined')
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
        <div class="infotag-group col-sm-3" v-for="group in groupedInfotags" :key="group.id" style="margin-top:5px">
 | 
			
		||||
            <div class="infotag-title">{{group.name}}</div>
 | 
			
		||||
            <hr>
 | 
			
		||||
            <infotag :infotag="infotag" v-for="infotag in group.infotags" :key="infotag.id"></infotag>
 | 
			
		||||
            <infotag :infotag="infotag" v-for="infotag in group.infotags" :key="infotag.id" :selfCharacter="selfCharacter"></infotag>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
@ -30,6 +30,8 @@
 | 
			
		||||
    export default class InfotagsView extends Vue {
 | 
			
		||||
        @Prop({required: true})
 | 
			
		||||
        private readonly character!: Character;
 | 
			
		||||
        @Prop({required: true})
 | 
			
		||||
        readonly selfCharacter!: Character;
 | 
			
		||||
 | 
			
		||||
        get groupedInfotags(): DisplayInfotagGroup[] {
 | 
			
		||||
            const groups = Store.kinks.infotag_groups;
 | 
			
		||||
 | 
			
		||||
@ -4,14 +4,14 @@
 | 
			
		||||
        <i v-show="kink.hasSubkinks" class="fa" :class="{'fa-minus': !listClosed, 'fa-plus': listClosed}"></i>
 | 
			
		||||
        <i v-show="!kink.hasSubkinks && kink.isCustom" class="far custom-kink-icon"></i>
 | 
			
		||||
        <span class="kink-name">{{ kink.name }}</span>
 | 
			
		||||
        <span class="kink-custom-desc" v-if="(kink.isCustom)">{{kink.description}}</span>
 | 
			
		||||
        <span class="kink-custom-desc" v-if="((kink.isCustom) && (expandedCustom))">{{kink.description}}</span>
 | 
			
		||||
        <template v-if="kink.hasSubkinks">
 | 
			
		||||
            <div class="subkink-list" :class="{closed: this.listClosed}">
 | 
			
		||||
                <kink v-for="subkink in kink.subkinks" :kink="subkink" :key="subkink.id" :comparisons="comparisons"
 | 
			
		||||
                    :highlights="highlights"></kink>
 | 
			
		||||
            </div>
 | 
			
		||||
        </template>
 | 
			
		||||
        <div class="popover popover-top" v-if="((showTooltip) && (!kink.isCustom))" style="display:block;bottom:100%;top:initial;margin-bottom:5px">
 | 
			
		||||
        <div class="popover popover-top" v-if="((showTooltip) && ((!kink.isCustom) || (!expandedCustom)))" style="display:block;bottom:100%;top:initial;margin-bottom:5px">
 | 
			
		||||
            <div class="arrow" style="left:10%"></div>
 | 
			
		||||
            <h5 class="popover-header">{{kink.name}}</h5>
 | 
			
		||||
            <div class="popover-body"><p>{{kink.description}}</p></div>
 | 
			
		||||
@ -34,9 +34,17 @@
 | 
			
		||||
        readonly highlights!: {[key: number]: boolean};
 | 
			
		||||
        @Prop({required: true})
 | 
			
		||||
        readonly comparisons!: {[key: number]: string | undefined};
 | 
			
		||||
        @Prop({required: false})
 | 
			
		||||
        expandedCustom: boolean = false;
 | 
			
		||||
 | 
			
		||||
        listClosed = true;
 | 
			
		||||
        showTooltip = false;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        toggleExpandedCustoms(): void {
 | 
			
		||||
            this.expandedCustom = !this.expandedCustom;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        toggleSubkinks(): void {
 | 
			
		||||
            if(!this.kink.hasSubkinks)
 | 
			
		||||
                return;
 | 
			
		||||
@ -52,7 +60,8 @@
 | 
			
		||||
                'stock-kink': !this.kink.isCustom,
 | 
			
		||||
                'custom-kink': this.kink.isCustom,
 | 
			
		||||
                highlighted: !this.kink.isCustom && this.highlights[this.kink.id],
 | 
			
		||||
                subkink: this.kink.hasSubkinks
 | 
			
		||||
                subkink: this.kink.hasSubkinks,
 | 
			
		||||
                'expanded-custom-kink': this.expandedCustom,
 | 
			
		||||
            };
 | 
			
		||||
            classes[`kink-id-${this.kinkId}`] = true;
 | 
			
		||||
            classes[`kink-group-${this.kink.group}`] = true;
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,17 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="character-kinks-block" @contextmenu="contextMenu" @touchstart="contextMenu" @touchend="contextMenu">
 | 
			
		||||
        <div class="compare-highlight-block d-flex justify-content-between">
 | 
			
		||||
            <div class="expand-custom-kinks-block form-inline">
 | 
			
		||||
                <button class="btn btn-primary" @click="toggleExpandedCustomKinks" :disabled="loading">Expand Custom Kinks</button>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div v-if="shared.authenticated" class="quick-compare-block form-inline">
 | 
			
		||||
                <character-select v-model="characterToCompare"></character-select>
 | 
			
		||||
                <button class="btn btn-primary" @click="compareKinks" :disabled="loading || !characterToCompare">
 | 
			
		||||
                <button class="btn btn-outline-secondary" @click="compareKinks" :disabled="loading || !characterToCompare">
 | 
			
		||||
                    {{ compareButtonText }}
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="form-inline">
 | 
			
		||||
                <select v-model="highlightGroup" class="form-control">
 | 
			
		||||
                    <option :value="undefined">None</option>
 | 
			
		||||
@ -21,7 +26,7 @@
 | 
			
		||||
                        <h4>Favorites</h4>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="card-body">
 | 
			
		||||
                        <kink v-for="kink in groupedKinks['favorite']" :kink="kink" :key="kink.id" :highlights="highlighting"
 | 
			
		||||
                        <kink v-for="kink in groupedKinks['favorite']" :kink="kink" :key="kink.id" :highlights="highlighting" :expandedCustom="expandedCustoms"
 | 
			
		||||
                            :comparisons="comparison"></kink>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
@ -32,7 +37,7 @@
 | 
			
		||||
                        <h4>Yes</h4>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="card-body">
 | 
			
		||||
                        <kink v-for="kink in groupedKinks['yes']" :kink="kink" :key="kink.id" :highlights="highlighting"
 | 
			
		||||
                        <kink v-for="kink in groupedKinks['yes']" :kink="kink" :key="kink.id" :highlights="highlighting" :expandedCustom="expandedCustoms"
 | 
			
		||||
                            :comparisons="comparison"></kink>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
@ -43,7 +48,7 @@
 | 
			
		||||
                        <h4>Maybe</h4>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="card-body">
 | 
			
		||||
                        <kink v-for="kink in groupedKinks['maybe']" :kink="kink" :key="kink.id" :highlights="highlighting"
 | 
			
		||||
                        <kink v-for="kink in groupedKinks['maybe']" :kink="kink" :key="kink.id" :highlights="highlighting" :expandedCustom="expandedCustoms"
 | 
			
		||||
                            :comparisons="comparison"></kink>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
@ -54,7 +59,7 @@
 | 
			
		||||
                        <h4>No</h4>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="card-body">
 | 
			
		||||
                        <kink v-for="kink in groupedKinks['no']" :kink="kink" :key="kink.id" :highlights="highlighting"
 | 
			
		||||
                        <kink v-for="kink in groupedKinks['no']" :kink="kink" :key="kink.id" :highlights="highlighting" :expandedCustom="expandedCustoms"
 | 
			
		||||
                            :comparisons="comparison"></kink>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
@ -91,6 +96,13 @@
 | 
			
		||||
        highlighting: {[key: string]: boolean} = {};
 | 
			
		||||
        comparison: {[key: string]: KinkChoice} = {};
 | 
			
		||||
 | 
			
		||||
        expandedCustoms = false;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        toggleExpandedCustomKinks(): void {
 | 
			
		||||
            this.expandedCustoms = !this.expandedCustoms;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async compareKinks(): Promise<void> {
 | 
			
		||||
            if(this.comparing) {
 | 
			
		||||
                this.comparison = {};
 | 
			
		||||
 | 
			
		||||
@ -38,12 +38,13 @@
 | 
			
		||||
                <i class="far fa-envelope fa-fw"></i>Send Note</a>
 | 
			
		||||
            <div v-if="character.character.online_chat" @click="showInChat()" class="character-page-online-chat">Online In Chat</div>
 | 
			
		||||
 | 
			
		||||
            <div class="contact-block">
 | 
			
		||||
                <contact-method v-for="method in contactMethods" :method="method" :key="method.id"></contact-method>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="quick-info-block">
 | 
			
		||||
                <infotag-item v-for="infotag in quickInfoItems" :infotag="infotag" :key="infotag.id"></infotag-item>
 | 
			
		||||
                <infotag-item v-for="infotag in quickInfoItems" :infotag="infotag" :key="infotag.id" :selfCharacter="selfCharacter"></infotag-item>
 | 
			
		||||
 | 
			
		||||
                <div class="contact-block">
 | 
			
		||||
                    <contact-method v-for="method in contactMethods" :method="method" :key="method.id"></contact-method>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="quick-info">
 | 
			
		||||
                    <span class="quick-info-label">Created</span>
 | 
			
		||||
                    <span class="quick-info-value"><date :time="character.character.created_at"></date></span>
 | 
			
		||||
@ -141,6 +142,9 @@
 | 
			
		||||
        readonly character!: Character;
 | 
			
		||||
        @Prop()
 | 
			
		||||
        readonly oldApi?: true;
 | 
			
		||||
        @Prop({required: true})
 | 
			
		||||
        readonly selfCharacter!: Character;
 | 
			
		||||
 | 
			
		||||
        readonly shared: SharedStore = Store;
 | 
			
		||||
        readonly quickInfoIds: ReadonlyArray<number> = [1, 3, 2, 49, 9, 29, 15, 41, 25]; // Do not sort these.
 | 
			
		||||
        readonly avatarUrl = Utils.avatarURL;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user