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();
|
||||
}
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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…
Reference in New Issue