forked from e621ng/e621ng
Move post uploader to vue.js and inline it
This commit is contained in:
parent
ac2de04d9e
commit
1636b62c3e
@ -52,7 +52,13 @@ class UploadsController < ApplicationController
|
||||
flash[:notice] = @service.warnings.join(".\n \n")
|
||||
end
|
||||
|
||||
respond_with(@upload)
|
||||
respond_with(@upload) do |format|
|
||||
format.json do
|
||||
return render json: {success: true, location: post_path(@upload.post_id), post_id: @upload.post_id} unless @upload.is_errored?
|
||||
return render json: {success: false, reason: 'duplicate', location: post_path(@upload.duplicate_post_id), post_id: @upload.duplicate_post_id}, status: 412 if @upload.is_duplicate?
|
||||
return render json: {success: false, reason: 'invalid', message: @upload.sanitized_status}, status: 412 if @upload.is_errored?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -45,4 +45,5 @@ export { default as Upload } from '../src/javascripts/uploads.js';
|
||||
export { default as Utility } from '../src/javascripts/utility.js';
|
||||
export { default as Ugoira } from '../src/javascripts/ugoira.js';
|
||||
export { default as Takedown } from '../src/javascripts/takedowns.js';
|
||||
export { default as Thumbnails } from '../src/javascripts/thumbnails.js';
|
||||
export { default as Thumbnails } from '../src/javascripts/thumbnails.js';
|
||||
export { default as Uploader } from '../src/javascripts/uploader.js';
|
||||
|
12
app/javascript/src/javascripts/uploader.js
Normal file
12
app/javascript/src/javascripts/uploader.js
Normal file
@ -0,0 +1,12 @@
|
||||
import Uploader from './uploader.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
init() {
|
||||
const app = new Vue({
|
||||
render: (h) => h(Uploader)
|
||||
});
|
||||
|
||||
app.$mount('#uploader');
|
||||
}
|
||||
}
|
898
app/javascript/src/javascripts/uploader.vue
Normal file
898
app/javascript/src/javascripts/uploader.vue
Normal file
@ -0,0 +1,898 @@
|
||||
<template>
|
||||
<div class="flex-grid-outer">
|
||||
<div class="col box-section" style="flex: 2 0 0;">
|
||||
<div class="the_secret_switch" @click="normalMode = !normalMode"></div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_file">Image</label>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div v-if="!disableFileUpload">
|
||||
<label>File:
|
||||
<input type="file" ref="post_file" @change="updatePreview" :disabled="disableFileUpload"/>
|
||||
</label>
|
||||
<button @click="clearFile" v-show="disableURLUpload">Clear</button>
|
||||
</div>
|
||||
<div v-if="!disableURLUpload">
|
||||
<div class="box-section sect_red" v-if="badDirectURL">
|
||||
The direct URL entered has the following problem: {{ directURLProblem }}<br>
|
||||
You should review <a href="/wiki/show/howto:sites_and_sources">the sourcing guide</a>.
|
||||
</div>
|
||||
<label>{{!disableFileUpload ? '(or) ' : '' }}URL:
|
||||
<input type="text" size="50" v-model="uploadURL" @keyup="updatePreview"
|
||||
:disabled="disableURLUpload"/>
|
||||
</label>
|
||||
<div id="whitelist-warning" v-show="whitelist.visible"
|
||||
:class="{'whitelist-warning-allowed': whitelist.allowed, 'whitelist-warning-disallowed': !whitelist.allowed}">
|
||||
<span v-if="whitelist.allowed">Uploads from <b>{{whitelist.domain}}</b> are permitted.</span>
|
||||
<span v-if="!whitelist.allowed">Uploads from <b>{{whitelist.domain}}</b> are not permitted. (<a
|
||||
href='/upload_whitelist'>View whitelisted domains</a>)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-section upload_preview_container in-editor below-upload">
|
||||
<div class="upload_preview_dims">{{ previewDimensions }}</div>
|
||||
<img class="upload_preview_img" :src="previewURL" style="max-width: 100%;"/>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_sources">Sources</label>
|
||||
<div>You should include: A link to the artists page where this was obtained, and a link to the
|
||||
submission page
|
||||
where this image was obtained. No available source should ONLY be used if the content has never
|
||||
been posted
|
||||
online anywhere else.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div class="box-section sect_red" v-show="showErrors && sourceWarning">A source must be provided or
|
||||
you must
|
||||
select that there
|
||||
is no available source.
|
||||
</div>
|
||||
<div v-if="!noSource">
|
||||
<image-source :last="i === (sources.length-1)" :index="i" v-model="sources[i]"
|
||||
v-for="s, i in sources"
|
||||
@delete="removeSource(i)" @add="addSource" :key="i"></image-source>
|
||||
</div>
|
||||
<div>
|
||||
<label class="section-label"><input type="checkbox" id="no_source" v-model="noSource"/> No
|
||||
available source
|
||||
/ I am the source.</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="normalMode">
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="names">Artists</label>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div>
|
||||
<textarea class="tag-textarea" v-model="tagEntries.character" id="post_characters" rows="2"
|
||||
placeholder="Ex: artist_name etc."></textarea>
|
||||
</div>
|
||||
<div class="flex-wrap">
|
||||
<image-checkbox :check="check" :checks="checkboxes.selected"
|
||||
v-for="check in checkboxes.artist" @set="setCheck"
|
||||
:key="check.name"></image-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_sex_tags">Characters</label>
|
||||
<div>Select (and write in) all that apply. Character sex is based only on what is visible in the
|
||||
image.
|
||||
Outside information or other images should not be used when deciding what tags are used.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div class="flex-wrap">
|
||||
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.sex"
|
||||
@set="setCheck"
|
||||
:key="check.name"></image-checkbox>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="flex-wrap">
|
||||
<image-checkbox :check="check" :checks="checkboxes.selected"
|
||||
v-for="check in checkboxes.count" @set="setCheck"
|
||||
:key="check.name"></image-checkbox>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="flex-wrap">
|
||||
<image-checkbox :check="check" :checks="checkboxes.selected"
|
||||
v-for="check in checkboxes.pairing" @set="setCheck"
|
||||
:key="check.name"></image-checkbox>
|
||||
</div>
|
||||
<textarea class="tag-textarea" rows="2" v-model="tagEntries.sex" id="post_sexes"
|
||||
placeholder="Ex: character_name solo_focus etc."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label">Body Types and Species</label>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div class="flex-wrap">
|
||||
<image-checkbox :check="check" :checks="checkboxes.selected"
|
||||
v-for="check in checkboxes.body" @set="setCheck"
|
||||
:key="check.name"></image-checkbox>
|
||||
</div>
|
||||
<textarea class="tag-textarea" rows="2" v-model="tagEntries.bodyType" id="post_bodyTypes"
|
||||
placeholder="Ex: bear dragon hyena rat newt etc."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label">Contentious Content</label>
|
||||
<div>
|
||||
These allow users to find or blacklist content with ease. Make sure that you are tagging
|
||||
these upon
|
||||
initial upload.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<textarea class="tag-textarea" v-model="tagEntries.theme" id="post_themes" rows="2"
|
||||
placeholder="Ex: cub young gore scat watersports diaper my_little_pony vore not_furry rape etc."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_rating_questionable">Rating</label>
|
||||
<div>Explicit tags include sex, pussy, penis, masturbation, fellatio, etc.
|
||||
(<%= link_to "help", help_pages_path(title: 'ratings'), target: "_blank" %>)
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div class="box-section sect_red" v-if="showErrors && invalidRating">
|
||||
You must select an appropriate rating for this image.
|
||||
</div>
|
||||
<div>
|
||||
<template v-if="!safe">
|
||||
<button class="toggle-button" :class="{active: rating==='e'}" @click="rating = 'e'">
|
||||
Explicit
|
||||
</button>
|
||||
<button class="toggle-button" :class="{active: rating==='q'}" @click="rating = 'q'">
|
||||
Questionable
|
||||
</button>
|
||||
</template>
|
||||
<button class="toggle-button" :class="{active: rating==='s'}" @click="rating = 's'">Safe
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid come-together-now">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_tags">Other Tags</label>
|
||||
<div>
|
||||
Separate tags with spaces. (<%= link_to "help", help_pages_path(title: 'tags'), target: "_blank"
|
||||
%>)
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div class="box-section upload_preview_container in-editor">
|
||||
<div class="upload_preview_dims">{{ previewDimensions }}</div>
|
||||
<img class="upload_preview_img" :src="previewURL" style="max-width: 100%;"/>
|
||||
</div>
|
||||
<div class="box-section sect_red" v-show="showErrors && notEnoughTags">
|
||||
You must provide at least <b>{{4 - tagCount}}</b> more tags. Tags in other sections count
|
||||
towards this
|
||||
total.
|
||||
</div>
|
||||
<div v-show="!preview.show">
|
||||
<textarea class="tag-textarea" id="post_tags" v-model="tagEntries.other" rows="5"
|
||||
ref="otherTags"></textarea>
|
||||
</div>
|
||||
<div v-show="preview.show">
|
||||
<tag-preview :tags="preview.tags" :loading="preview.loading"
|
||||
@close="previewFinalTags"></tag-preview>
|
||||
</div>
|
||||
<div class="related-tag-functions">
|
||||
<a href="#" @click.prevent="findRelated()">Related Tags</a> |
|
||||
<a href="#" @click.prevent="findRelated('artist')">Related Artists</a> |
|
||||
<a href="#" @click.prevent="findRelated('char')">Related Characters</a> |
|
||||
<a href="#" @click.prevent="findRelated('copyright')">Related Copyrights</a> |
|
||||
<a href="#" @click.prevent="previewFinalTags">Preview Final Tags</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom over-me">
|
||||
<related-tags :tags="tagsArray" :related="relatedTags" :loading="loadingRelated"
|
||||
@tag-active="pushTag"></related-tags>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label">Parent Post ID</label>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<input type="number" v-model.number="parentID" placeholder="Ex. 12345"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_description">Description</label>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<textarea class="tag-textarea" id="post_description" v-model="description" rows="5"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid">
|
||||
<div class="col"></div>
|
||||
<div class="col2">
|
||||
<div class="box-section sect_red" v-show="preventUpload && showErrors">
|
||||
Unmet requirements above prevent the submission of the post.
|
||||
</div>
|
||||
<div class="box-section sect_green" v-show="submitting">
|
||||
Submitting your post, please wait.
|
||||
</div>
|
||||
<div class="box-section sect_red" v-show="error">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div class="box-section sect_red" v-show="duplicateId">
|
||||
Post is a duplicate of <a :href="duplicatePath">post #{{duplicateId}}.</a>
|
||||
</div>
|
||||
<button @click="submit" :disabled="(showErrors && preventUpload) || submitting" accesskey="s">{{
|
||||
submitting ? 'Uploading...' :
|
||||
'Upload' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="preview-sidebar" class="col box-section" style="margin-left: 10px; padding: 10px;">
|
||||
<div class="upload_preview_container in-sidebar">
|
||||
<div class="upload_preview_dims">{{ previewDimensions }}</div>
|
||||
<img class="upload_preview_img" :src="previewURL" style="max-width: 100%;" @load="updatePreviewDims"
|
||||
@error="previewError"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.toggle-button {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: center;
|
||||
user-select: none;
|
||||
padding: .175em .5rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
border-radius: 0.25rem;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.toggle-button.active {
|
||||
background-color: #FDF5D9;
|
||||
}
|
||||
|
||||
.flex-grid-outer {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.upload_preview_container.in-editor {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upload_preview_container.in-sidebar {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.flex-grid {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.col {
|
||||
flex: 1 0 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.col2 {
|
||||
flex: 2 1 0;
|
||||
}
|
||||
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.come-together-now {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.over-me {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.related-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 0 1 10%;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.related-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 -5px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.related-item {
|
||||
padding: 0 5px;
|
||||
max-width: 140px;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.related-title {
|
||||
padding: 0 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tag-active {
|
||||
background: rgb(0, 111, 250);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag-preview {
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
background: rgba(1, 1, 1, 0.15);
|
||||
border-radius: 2px;
|
||||
padding: 3px;
|
||||
margin-right: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tag-preview-alias {
|
||||
background-color: rgba(150, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.tag-preview-implication {
|
||||
background-color: rgba(0, 150, 0, 0.25);
|
||||
}
|
||||
|
||||
.tag-textarea {
|
||||
display: inline-block; /* Why were we even unsetting this? It breaks EVERYTHING. */
|
||||
font-size: 1rem;
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* Need to override this so it shows up at all. */
|
||||
#whitelist-warning {
|
||||
display: block;
|
||||
float: none;
|
||||
}
|
||||
|
||||
@media only screen and (orientation: portrait), (max-width: 1100px) {
|
||||
#preview-sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upload_preview_container.in-editor {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.upload_preview_container.in-sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upload_preview_dims {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.upload_preview_img {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.below-upload > .upload_preview_img {
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
.related-section {
|
||||
flex: 0 1 50%;
|
||||
}
|
||||
|
||||
.flex-grid {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.the_secret_switch {
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import source from './uploader_source.vue';
|
||||
import checkbox from './uploader_checkbox.vue';
|
||||
import relatedTags from './uploader_related.vue';
|
||||
import tagPreview from './uploader_preview.vue';
|
||||
|
||||
const thumbURLs = [
|
||||
"/images/notfound-preview.png",
|
||||
"/images/download-preview.png",
|
||||
"/images/webm-preview.png",
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
||||
];
|
||||
const thumbs = {
|
||||
webm: "/images/webm-preview.png",
|
||||
flash: "/images/download-preview.png",
|
||||
notfound: "/images/notfound-preview.png",
|
||||
none: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
|
||||
};
|
||||
const artist_checks = [
|
||||
{name: 'Unknown Artist'},
|
||||
{name: 'Anonymous Artist'}];
|
||||
|
||||
const sex_checks = [
|
||||
{name: 'Male'},
|
||||
{name: 'Female'},
|
||||
{name: 'Andromorph'},
|
||||
{name: 'Gynomorph'},
|
||||
{name: 'Hermaphrodite', tag: 'herm'},
|
||||
{name: 'Male-Herm', tag: 'maleherm'},
|
||||
{name: 'Ambiguous', tag: 'ambiguous_gender'}];
|
||||
|
||||
const pairing_checks = [
|
||||
{name: 'Male/Male'},
|
||||
{name: 'Male/Female'},
|
||||
{name: 'Female/Female'},
|
||||
{name: 'Intersex/Male'},
|
||||
{name: 'Intersex/Female'},
|
||||
{name: 'Intersex/Intersex'}
|
||||
];
|
||||
|
||||
const char_count_checks = [
|
||||
{name: 'Solo'},
|
||||
{name: 'Duo'},
|
||||
{name: 'Group'},
|
||||
{name: 'Zero Pictured'}];
|
||||
|
||||
const body_type_checks = [
|
||||
{name: 'Anthro'},
|
||||
{name: 'Feral'},
|
||||
{name: 'Humanoid'},
|
||||
{name: 'Human'},
|
||||
{name: 'Taur'}];
|
||||
|
||||
function updatePreviewDims(e) {
|
||||
var img = e.target;
|
||||
if (thumbURLs.filter(function (x) {
|
||||
return img.src.indexOf(x) !== -1;
|
||||
}).length !== 0)
|
||||
return;
|
||||
this.previewHeight = img.naturalHeight;
|
||||
this.previewWidth = img.naturalWidth;
|
||||
this.overDims = (img.naturalHeight > 15000 || img.naturalWidth > 15000);
|
||||
updateDimensionTag.call(this);
|
||||
}
|
||||
|
||||
function previewError() {
|
||||
this.previewWidth = this.previewHeight = 0;
|
||||
this.overDims = false;
|
||||
if (this.uploadURL === '' && !this.$refs['post_file']) {
|
||||
this.previewURL = thumbs.none;
|
||||
} else {
|
||||
this.previewURL = thumbs.notfound;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreviewFile() {
|
||||
var self = this;
|
||||
var reader = new FileReader();
|
||||
var file = this.$refs['post_file'].files[0];
|
||||
this.previewHeight = 0;
|
||||
this.previewWidth = 0;
|
||||
reader.onload = function (e) {
|
||||
var src = e.target.result;
|
||||
|
||||
if (file.type.match('video/webm'))
|
||||
src = thumbs.webm;
|
||||
else if (file.type.match('application/x-shockwave-flash'))
|
||||
src = thumbs.flash;
|
||||
self.previewURL = src;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
this.disableURLUpload = true;
|
||||
}
|
||||
|
||||
function updatePreviewURL() {
|
||||
var self = this;
|
||||
if (this.uploadURL.length === 0 || (this.$refs['post_file'] && this.$refs['post_file'].files.length > 0)) {
|
||||
this.disableFileUpload = false;
|
||||
this.oldDomain = '';
|
||||
return;
|
||||
}
|
||||
this.disableFileUpload = true;
|
||||
var domain = $j("<a>").prop("href", this.uploadURL).prop("hostname");
|
||||
|
||||
if (domain && domain != this.oldDomain) {
|
||||
$j.getJSON("/upload_whitelist/is_whitelisted", {url: this.uploadURL}, function (data) {
|
||||
if (data.domain)
|
||||
self.whitelistWarning(data.is_whitelisted, data.domain);
|
||||
});
|
||||
}
|
||||
this.oldDomain = domain;
|
||||
|
||||
var src = thumbs.none;
|
||||
if (this.uploadURL.match(/^(https?\:\/\/|www).*?\.(jpg|jpeg|gif|png)/))
|
||||
src = this.uploadURL;
|
||||
else if (this.uploadURL.match(/^(https?\:\/\/|www).*?\.(swf)$/))
|
||||
src = thumbs.flash;
|
||||
else if (this.uploadURL.match(/^(https?\:\/\/|www).*?\.(webm)$/))
|
||||
src = thumbs.webm;
|
||||
|
||||
this.previewURL = src;
|
||||
}
|
||||
|
||||
function updateDimensionTag() {
|
||||
var self = this;
|
||||
if (!(self.previewHeight || self.previewWidth))
|
||||
return;
|
||||
var otherTags = ['low_res', 'hi_res', 'superabsurd_res', 'absurd_res'];
|
||||
var ourTag = function (h, w) {
|
||||
if (!(h && w)) {
|
||||
return null;
|
||||
}
|
||||
if ((h <= 500) && (w <= 500))
|
||||
return 'low_res';
|
||||
if ((h >= 10000) || (w >= 10000))
|
||||
return 'superabsurb_res';
|
||||
if ((h >= 2400) || (w >= 3200))
|
||||
return 'absurd_res';
|
||||
if ((h >= 1200) || (w >= 1600))
|
||||
return 'hi_res';
|
||||
return null;
|
||||
}(self.previewHeight, self.previewWidth);
|
||||
var tagIdx = otherTags.indexOf(ourTag);
|
||||
if (tagIdx > 0)
|
||||
otherTags.splice(tagIdx, 1);
|
||||
if (ourTag)
|
||||
self.pushTag(ourTag, true);
|
||||
for (var i = 0; i < otherTags.length; i++) {
|
||||
self.pushTag(otherTags[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreview() {
|
||||
if (this.$refs['post_file'] && this.$refs['post_file'].files[0])
|
||||
updatePreviewFile.call(this);
|
||||
else
|
||||
updatePreviewURL.call(this);
|
||||
}
|
||||
|
||||
function directURLCheck(url) {
|
||||
var patterns = [{reason: 'Thumbnail URL', test: /[at]\.facdn\.net/gi},
|
||||
{reason: 'Sample URL', test: /pximg\.net\/img-master/gi},
|
||||
// {reason: 'Sample URL', test: /\d+\.media\.tumblr\.com/gi}, // Tumblr broke raws.
|
||||
{reason: 'Sample URL', test: /d3gz42uwgl1r1y\.cloudfront\.net\/.*\/\d+x\d+\./gi},
|
||||
{reason: 'Sample URL', test: /pbs\.twimg\.com\/media\/\w+\.(jpg|png)(:large)?$/gi},
|
||||
{reason: 'Sample URL', test: /pbs\.twimg\.com\/media\/\w+\?format=/gi},
|
||||
{reason: 'Sample URL', test: /derpicdn\.net\/.*\/large\./gi},
|
||||
{reason: 'Sample URL', test: /metapix\.net\/files\/(preview|screen)\//gi},
|
||||
{reason: 'Sample URL', test: /sofurryfiles\.com\/std\/preview/gi}];
|
||||
for (var i = 0; i < patterns.length; ++i) {
|
||||
var pattern = patterns[i];
|
||||
if (pattern.test.test(url))
|
||||
return pattern.reason;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function clearFileUpload() {
|
||||
if (!(this.$refs['post_file'] && this.$refs['post_file'].files[0]))
|
||||
return;
|
||||
this.$refs['post_file'].value = null;
|
||||
this.disableURLUpload = this.disableFileUpload = false;
|
||||
this.previewURL = thumbs.none;
|
||||
this.previewHeight = this.previewWidth = 0;
|
||||
this.updatePreview();
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
'image-source': source,
|
||||
'image-checkbox': checkbox,
|
||||
'related-tags': relatedTags,
|
||||
'tag-preview': tagPreview
|
||||
},
|
||||
data() {
|
||||
var allChecks = {};
|
||||
var addChecks = function (check) {
|
||||
if (typeof check['tag'] !== "undefined") {
|
||||
allChecks[check.tag] = true;
|
||||
return
|
||||
}
|
||||
allChecks[check.name.toLowerCase().replace(' ', '_')] = true;
|
||||
};
|
||||
artist_checks.forEach(addChecks);
|
||||
sex_checks.forEach(addChecks);
|
||||
pairing_checks.forEach(addChecks);
|
||||
char_count_checks.forEach(addChecks);
|
||||
body_type_checks.forEach(addChecks);
|
||||
return {
|
||||
safe: window.safeSite,
|
||||
showErrors: false,
|
||||
whitelist: {
|
||||
visible: false,
|
||||
allowed: false,
|
||||
domain: ''
|
||||
},
|
||||
submitting: false,
|
||||
disableFileUpload: false,
|
||||
disableURLUpload: false,
|
||||
|
||||
previewHeight: 0,
|
||||
previewWidth: 0,
|
||||
overDims: false,
|
||||
uploadURL: '',
|
||||
previewURL: thumbs.none,
|
||||
|
||||
oldDomain: '',
|
||||
|
||||
noSource: false,
|
||||
sources: [''],
|
||||
normalMode: true,
|
||||
|
||||
checkboxes: {
|
||||
artist: artist_checks,
|
||||
sex: sex_checks,
|
||||
pairing: pairing_checks,
|
||||
count: char_count_checks,
|
||||
body: body_type_checks,
|
||||
selected: {},
|
||||
all: allChecks
|
||||
},
|
||||
tagEntries: {
|
||||
character: '',
|
||||
sex: '',
|
||||
bodyType: '',
|
||||
theme: '',
|
||||
other: ''
|
||||
},
|
||||
|
||||
preview: {
|
||||
loading: false,
|
||||
show: false,
|
||||
tags: []
|
||||
},
|
||||
|
||||
relatedTags: [],
|
||||
loadingRelated: false,
|
||||
|
||||
parentID: '',
|
||||
description: '',
|
||||
rating: '',
|
||||
error: '',
|
||||
duplicateId: 0
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updatePreview: updatePreview,
|
||||
updatePreviewDims: updatePreviewDims,
|
||||
previewError: previewError,
|
||||
clearFile: clearFileUpload,
|
||||
whitelistWarning(allowed, domain) {
|
||||
this.whitelist.allowed = allowed;
|
||||
this.whitelist.domain = domain;
|
||||
this.whitelist.visible = true;
|
||||
},
|
||||
removeSource(i) {
|
||||
this.sources.splice(i, 1);
|
||||
},
|
||||
addSource() {
|
||||
if (this.sources.length < 5)
|
||||
this.sources.push('');
|
||||
},
|
||||
setCheck(tag, value) {
|
||||
Vue.set(this.checkboxes.selected, tag, value);
|
||||
},
|
||||
submit() {
|
||||
this.showErrors = true;
|
||||
this.error = '';
|
||||
if (this.preventUpload || this.submitting)
|
||||
return;
|
||||
var self = this;
|
||||
this.submitting = true;
|
||||
var data = new FormData();
|
||||
var post_file = this.$refs['post_file'];
|
||||
if (post_file && post_file.files && post_file.files.length) {
|
||||
data.append('upload[file]', this.$refs['post_file'].files[0]);
|
||||
} else {
|
||||
data.append('upload[direct_url]', this.uploadURL);
|
||||
}
|
||||
data.append('upload[tag_string]', this.tags);
|
||||
data.append('upload[rating]', this.rating);
|
||||
data.append('upload[source]', this.sources.join('\n'));
|
||||
// data.append('upload[description]', this.description);
|
||||
data.append('upload[parent_id]', this.parentID);
|
||||
jQuery.ajax('/uploads.json', {
|
||||
contentType: false,
|
||||
processData: false,
|
||||
method: 'POST',
|
||||
type: 'POST',
|
||||
data: data,
|
||||
success(data) {
|
||||
self.submitting = false;
|
||||
Danbooru.notice('Post uploaded successfully.');
|
||||
location.assign(data.location);
|
||||
},
|
||||
error(data) {
|
||||
self.submitting = false;
|
||||
const data2 = data.responseJSON;
|
||||
try {
|
||||
if (data2 && data2.reason === 'duplicate') {
|
||||
self.duplicateId = data2.post_id;
|
||||
} else {
|
||||
self.error = 'Error: ' + data2.messages.join('; ');
|
||||
}
|
||||
} catch (e) {
|
||||
self.error = 'Error: Unknown error! ' + data2;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
pushTag(tag, add) {
|
||||
this.preview.show = false;
|
||||
var isCheck = typeof this.checkboxes.all[tag] !== "undefined";
|
||||
// In advanced mode we need to push these into the tags area because there are no checkboxes or other
|
||||
// tag fields so we can't see them otherwise.
|
||||
if (isCheck && this.normalMode) {
|
||||
this.setCheck(tag, add);
|
||||
return;
|
||||
}
|
||||
var tags = this.tagEntries.other ? this.tagEntries.other.trim().split(' ') : [];
|
||||
var tagIdx = tags.indexOf(tag);
|
||||
if (add) {
|
||||
if (tagIdx === -1)
|
||||
tags.push(tag);
|
||||
} else {
|
||||
if (tagIdx === -1)
|
||||
return;
|
||||
tags.splice(tagIdx, 1);
|
||||
}
|
||||
this.tagEntries.other = tags.join(' ') + ' ';
|
||||
},
|
||||
previewFinalTags() {
|
||||
if (this.preview.loading)
|
||||
return;
|
||||
if (this.preview.show) {
|
||||
this.preview.show = false;
|
||||
return;
|
||||
}
|
||||
this.preview.loading = true;
|
||||
this.preview.show = true;
|
||||
this.preview.tags = [];
|
||||
var self = this;
|
||||
var data = {tags: this.tags};
|
||||
jQuery.ajax("/tags/preview.json", {
|
||||
method: 'POST',
|
||||
type: 'POST',
|
||||
data: data,
|
||||
success: function (result) {
|
||||
self.preview.loading = false;
|
||||
self.preview.tags = result;
|
||||
},
|
||||
error: function (result) {
|
||||
self.preview.loading = false;
|
||||
self.preview.tags = [];
|
||||
self.preview.show = false;
|
||||
Danbooru.error('Error loading tag preview ' + result);
|
||||
}
|
||||
})
|
||||
},
|
||||
findRelated(type) {
|
||||
const self = this;
|
||||
const convertResponse = function (respData) {
|
||||
var sortedRelated = [];
|
||||
for (var key in respData) {
|
||||
if (!respData.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
sortedRelated.push({title: 'Related: ' + key, tags: respData[key].sort(tagSorter)});
|
||||
}
|
||||
return sortedRelated;
|
||||
};
|
||||
const getSelectedTags = function () {
|
||||
const field = self.$refs['otherTags'];
|
||||
if (!field.hasOwnProperty('selectionStart'))
|
||||
return null;
|
||||
const length = field.selectionEnd - field.selectionStart;
|
||||
if (length)
|
||||
return field.value.substr(field.selectionStart, length);
|
||||
return null;
|
||||
};
|
||||
this.loadingRelated = true;
|
||||
this.relatedTags = [];
|
||||
const selectedTags = getSelectedTags();
|
||||
const params = selectedTags ? {query: selectedTags} : {query: this.tags};
|
||||
|
||||
if (type)
|
||||
params['category'] = type;
|
||||
$.getJSON("/related_tag/bulk.json", params, function (data) {
|
||||
self.relatedTags = convertResponse(data);
|
||||
}).always(function () {
|
||||
self.loadingRelated = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tags() {
|
||||
var self = this;
|
||||
var checked = Object.keys(this.checkboxes.selected).filter(function (x) {
|
||||
return self.checkboxes.selected[x] === true;
|
||||
});
|
||||
return checked.concat([this.tagEntries.other, this.tagEntries.sex, this.tagEntries.bodyType,
|
||||
this.tagEntries.theme, this.tagEntries.character]).join(' ').replace(',', ' ').trim().replace(/ +/g, ' ');
|
||||
},
|
||||
tagsArray() {
|
||||
return this.tags.toLowerCase().split(' ');
|
||||
},
|
||||
previewDimensions() {
|
||||
if (this.previewWidth && this.previewHeight)
|
||||
return this.previewHeight + '×' + this.previewWidth;
|
||||
return '';
|
||||
},
|
||||
directURLProblem: function () {
|
||||
return directURLCheck(this.uploadURL);
|
||||
},
|
||||
badDirectURL: function () {
|
||||
return !!this.directURLProblem;
|
||||
},
|
||||
sourceWarning: function () {
|
||||
var validSourceCount = this.sources.filter(function (i) {
|
||||
return i.length > 0;
|
||||
}).length;
|
||||
return !this.noSource && (validSourceCount === 0);
|
||||
},
|
||||
tagCount: function () {
|
||||
return this.tags.split(' ').filter(function (x) {
|
||||
return x;
|
||||
}).length;
|
||||
},
|
||||
notEnoughTags: function () {
|
||||
return this.tagCount < 4;
|
||||
},
|
||||
invalidRating: function () {
|
||||
return !this.rating;
|
||||
},
|
||||
preventUpload: function () {
|
||||
return this.sourceWarning || this.badDirectURL || this.notEnoughTags
|
||||
|| this.invalidRating;
|
||||
},
|
||||
duplicatePath: function () {
|
||||
return `/posts/${this.duplicateId}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
24
app/javascript/src/javascripts/uploader_checkbox.vue
Normal file
24
app/javascript/src/javascripts/uploader_checkbox.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<button class="toggle-button" :class="{active: value}" @click="value = !value">{{check.name}}</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['check', 'checks'],
|
||||
computed: {
|
||||
value: {
|
||||
get() {
|
||||
if (this.checks[this.tagName] === undefined)
|
||||
return false;
|
||||
return this.checks[this.tagName];
|
||||
},
|
||||
set(v) {
|
||||
this.$emit('set', this.tagName, v);
|
||||
}
|
||||
},
|
||||
tagName() {
|
||||
return this.check.tag || this.check.name.toLowerCase().replace(/ /g, '_');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
70
app/javascript/src/javascripts/uploader_preview.vue
Normal file
70
app/javascript/src/javascripts/uploader_preview.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-show="loading">Fetching tags...</div>
|
||||
<div class="related-tags flex-wrap">
|
||||
<div class="related-items" v-for="sTags, i in splitTags" :key="i">
|
||||
<tag-preview v-for="tag, $idx in sTags" :key="$idx" :tag="tag"></tag-preview>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#" @click.prevent="close">Close Preview</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
|
||||
const tagPreviewTag = Vue.extend({
|
||||
functional: true,
|
||||
props: ['tag'],
|
||||
render: function (h, ctx) {
|
||||
var tag = ctx.props.tag;
|
||||
switch (tag.type) {
|
||||
default:
|
||||
case 'tag':
|
||||
return h('a', {
|
||||
staticClass: 'tag-preview tag-type-' + tag.tagType
|
||||
}, tag.a);
|
||||
case 'alias':
|
||||
return h('span', {staticClass: 'tag-preview tag-preview-alias'}, [
|
||||
h('del', undefined, [
|
||||
h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.a)
|
||||
]), ' → ', h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.b)
|
||||
]);
|
||||
case 'implication':
|
||||
return h('span', {staticClass: 'tag-preview tag-preview-implication'}, [
|
||||
h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.a), ' ⇐ ', h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.b)
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
props: ['tags', 'loading'],
|
||||
components: {
|
||||
'tag-preview': tagPreviewTag
|
||||
},
|
||||
methods: {
|
||||
close: function () {
|
||||
this.$emit('close');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
splitTags: function () {
|
||||
var newTags = this.tags.concat([]);
|
||||
newTags.sort(function (a, b) {
|
||||
return a.a === b.a ? 0 : (a.a < b.a ? -1 : 1);
|
||||
});
|
||||
var chunkArray = function (arr, size) {
|
||||
var chunks = [];
|
||||
for (var i = 0; i < arr.length; i += size) {
|
||||
chunks.push(arr.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
return chunkArray(newTags, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
89
app/javascript/src/javascripts/uploader_related.vue
Normal file
89
app/javascript/src/javascripts/uploader_related.vue
Normal file
@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div class="related-tags flex-wrap">
|
||||
<div class="related-section" v-for="group in tagGroups" :key="group.title">
|
||||
<div class="related-items" v-for="tags, i in splitTags(group.tags)" :key="i">
|
||||
<div class="related-title" v-if="i === 0">{{group.title}}</div>
|
||||
<div class="related-item" v-for="tag in tags" :key="tag[0]">
|
||||
<a :class="tagClasses(tag)" :href="tagLink(tag)" @click.prevent="toggle(tag)">{{tag[0]}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
function tagSorter(a, b) {
|
||||
return a[0] > b[0] ? 1 : -1;
|
||||
}
|
||||
export default {
|
||||
props: ['tags', 'related', 'loading'],
|
||||
data: function () {
|
||||
return {
|
||||
uploaded: (window.userUploadTags || []),
|
||||
recent: (window.userRecentTags || []).sort(tagSorter),
|
||||
artists: (window.userArtistTags || []).sort(tagSorter)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggle: function (tag) {
|
||||
this.$emit('tag-active', tag[0], !this.tagActive(tag));
|
||||
},
|
||||
tagLink: function (tag) {
|
||||
return '/post/index?tags=' + encodeURIComponent(tag[0]);
|
||||
},
|
||||
tagActive: function (tag) {
|
||||
return this.tags.indexOf(tag[0]) !== -1;
|
||||
},
|
||||
tagClasses: function (tag) {
|
||||
var classes = {'tag-active': this.tagActive(tag)};
|
||||
classes['tag-type-' + tag[2]] = true;
|
||||
return classes;
|
||||
},
|
||||
splitTags: function (tags) {
|
||||
var chunkArray = function (arr, size) {
|
||||
var chunks = [];
|
||||
for (var i = 0; i < arr.length; i += size) {
|
||||
chunks.push(arr.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
return chunkArray(tags, 15);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tagGroups: {
|
||||
get: function () {
|
||||
var groups = [];
|
||||
if (this.uploaded && this.uploaded.length) {
|
||||
groups.push({
|
||||
title: "Quick Tags",
|
||||
tags: this.uploaded
|
||||
});
|
||||
}
|
||||
if (this.recent && this.recent.length) {
|
||||
groups.push({
|
||||
title: "Recent",
|
||||
tags: this.recent
|
||||
});
|
||||
}
|
||||
if (this.artists && this.artists.length) {
|
||||
groups.push({
|
||||
title: "Artists",
|
||||
tags: this.artists
|
||||
});
|
||||
}
|
||||
if (this.related && this.related.length) {
|
||||
for (var i = 0; i < this.related.length; i++) {
|
||||
groups.push(this.related[i]);
|
||||
}
|
||||
}
|
||||
if (this.loading) {
|
||||
groups.push({title: 'Loading Related Tags', tags: [['', '', '']]});
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
42
app/javascript/src/javascripts/uploader_source.vue
Normal file
42
app/javascript/src/javascripts/uploader_source.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<input type="text" size="50" v-model="realValue" @keyup.enter="add"/>
|
||||
<button @click="remove" v-if="index !== 0">-</button>
|
||||
<button @click="add" v-if="last && index < 4">+</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['value', 'index', 'last'],
|
||||
data() {
|
||||
return {
|
||||
backendValue: this.value
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
'realValue': {
|
||||
get: function () {
|
||||
return this.backendValue;
|
||||
},
|
||||
set: function (v) {
|
||||
this.backendValue = v;
|
||||
this.$emit('input', v);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add() {
|
||||
this.$emit('add');
|
||||
},
|
||||
remove() {
|
||||
this.$emit('delete');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(v) {
|
||||
this.backendValue = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -156,6 +156,21 @@ class UserPresenter
|
||||
user.user_name_change_requests.map { |req| template.link_to req.original_name, req }.join(", ").html_safe
|
||||
end
|
||||
|
||||
def favorite_tags_with_types
|
||||
tag_names = user&.favorite_tags.to_s.split
|
||||
tag_names = TagAlias.to_aliased(tag_names)
|
||||
Tag.where(name: tag_names).map {|x| [x.name, x.post_count, x.category]}
|
||||
end
|
||||
|
||||
def recent_tags_with_types
|
||||
versions = PostArchive.where(updater_id: user.id).where("updated_at > ?", 1.hour.ago).order(id: :desc).limit(150)
|
||||
tags = versions.flat_map(&:added_tags)
|
||||
tags = tags.reject { |tag| Tag.is_metatag?(tag) }
|
||||
tags = tags.group_by(&:itself).transform_values(&:size).sort_by { |tag, count| [-count, tag] }.map(&:first)
|
||||
tags = tags.take(50)
|
||||
Tag.where(name: tags).map {|x| [x.name, x.post_count, x.category]}
|
||||
end
|
||||
|
||||
def custom_css
|
||||
user.custom_style.to_s.split(/\r\n|\r|\n/).map do |line|
|
||||
if line =~ /\A@import/
|
||||
|
@ -1,8 +1,3 @@
|
||||
<%= render partial: "uploads/upload_partials/css" %>
|
||||
<%= render partial: "uploads/upload_partials/tag_preview" %>
|
||||
<%= render partial: "uploads/upload_partials/source" %>
|
||||
<%= render partial: "uploads/upload_partials/related" %>
|
||||
<%= render partial: "uploads/upload_partials/uploader" %>
|
||||
<div id="post-add">
|
||||
<div style="margin-bottom: 1rem;" class="section">
|
||||
<h2>Before uploading, read
|
||||
@ -14,6 +9,7 @@
|
||||
Unsure what to tag your post
|
||||
with? <%= link_to "Tagging Checklist", help_pages_path(id: "tagging_checklist") %></p>
|
||||
</div>
|
||||
<div id="uploader"></div>
|
||||
<%# if current_user.is_member_or_lower? && (current_user.upload_limit <= 5 || current_user.post_upload_throttle <= 5) %>
|
||||
<!-- <div id="post-uploads-remaining" class="section<%# if [current_user.upload_limit, current_user.post_upload_throttle].min <= 0 %> sect_red<%# end %>" style="width:640px;">-->
|
||||
<!-- <p>-->
|
||||
@ -32,655 +28,12 @@
|
||||
<!-- See <%#= link_to "here", controller: "user", action: "upload_limit" %> for more details.-->
|
||||
<!-- </div>-->
|
||||
<%# end %>
|
||||
<post-creator :safe="<%= CurrentUser.safe_mode? %>"></post-creator>
|
||||
</div>
|
||||
<!--<script type="text/javascript">-->
|
||||
<!-- var userUploadTags = <%#= current_user.uploaded_tags_with_types.to_json %>;-->
|
||||
<!-- var userRecentTags = <%#= current_user.recent_tags_with_types.to_json %>;-->
|
||||
<!-- var userArtistTags = <%#= @artists.to_json %>;-->
|
||||
<!--</script>-->
|
||||
<script>
|
||||
var userUploadTags = [];
|
||||
var userRecentTags = [];
|
||||
var userArtistTags = [];
|
||||
var safeSite = <%= CurrentUser.safe_mode?.to_json %>;
|
||||
var userUploadTags = <%= CurrentUser.presenter.favorite_tags_with_types.to_json.html_safe %>;
|
||||
var userRecentTags = <%= CurrentUser.presenter.recent_tags_with_types.to_json.html_safe %>;
|
||||
</script>
|
||||
<script src="/vendor/vue.js"></script>
|
||||
<script type="text/javascript">
|
||||
const thumbURLs = [
|
||||
"/images/notfound-preview.png",
|
||||
"/images/download-preview.png",
|
||||
"/images/webm-preview.png",
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
||||
];
|
||||
const thumbs = {
|
||||
webm: "/images/webm-preview.png",
|
||||
flash: "/images/download-preview.png",
|
||||
notfound: "/images/notfound-preview.png",
|
||||
none: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
|
||||
};
|
||||
const artist_checks = [
|
||||
{name: 'Unknown Artist'},
|
||||
{name: 'Anonymous Artist'}];
|
||||
|
||||
const sex_checks = [
|
||||
{name: 'Male'},
|
||||
{name: 'Female'},
|
||||
{name: 'Cuntboy'},
|
||||
{name: 'Dickgirl'},
|
||||
{name: 'Hermaphrodite', tag: 'herm'},
|
||||
{name: 'Male-Herm', tag: 'maleherm'},
|
||||
{name: 'Ambiguous', tag: 'ambiguous_gender'}];
|
||||
|
||||
const pairing_checks = [
|
||||
{name: 'Male/Male'},
|
||||
{name: 'Male/Female'},
|
||||
{name: 'Female/Female'},
|
||||
{name: 'Intersex/Male'},
|
||||
{name: 'Intersex/Female'},
|
||||
{name: 'Intersex/Intersex'}
|
||||
];
|
||||
|
||||
const char_count_checks = [
|
||||
{name: 'Solo'},
|
||||
{name: 'Duo'},
|
||||
{name: 'Group'},
|
||||
{name: 'Zero Pictured'}];
|
||||
|
||||
const body_type_checks = [
|
||||
{name: 'Anthro'},
|
||||
{name: 'Feral'},
|
||||
{name: 'Humanoid'},
|
||||
{name: 'Human'},
|
||||
{name: 'Taur'}];
|
||||
|
||||
function updatePreviewDims(e) {
|
||||
var img = e.target;
|
||||
if (thumbURLs.filter(function (x) {
|
||||
return img.src.indexOf(x) !== -1;
|
||||
}).length !== 0)
|
||||
return;
|
||||
this.previewHeight = img.naturalHeight;
|
||||
this.previewWidth = img.naturalWidth;
|
||||
this.overDims = (img.naturalHeight > 15000 || img.naturalWidth > 15000);
|
||||
updateDimensionTag.call(this);
|
||||
}
|
||||
|
||||
function previewError() {
|
||||
this.previewWidth = this.previewHeight = 0;
|
||||
this.overDims = false;
|
||||
if (this.uploadURL === '' && !this.$refs['post_file']) {
|
||||
this.previewURL = thumbs.none;
|
||||
} else {
|
||||
this.previewURL = thumbs.notfound;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreviewFile() {
|
||||
var self = this;
|
||||
var reader = new FileReader();
|
||||
var file = this.$refs['post_file'].files[0];
|
||||
this.previewHeight = 0;
|
||||
this.previewWidth = 0;
|
||||
reader.onload = function (e) {
|
||||
var src = e.target.result;
|
||||
|
||||
if (file.type.match('video/webm'))
|
||||
src = thumbs.webm;
|
||||
else if (file.type.match('application/x-shockwave-flash'))
|
||||
src = thumbs.flash;
|
||||
self.previewURL = src;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
this.disableURLUpload = true;
|
||||
}
|
||||
|
||||
function updatePreviewURL() {
|
||||
var self = this;
|
||||
if (this.uploadURL.length === 0 || (this.$refs['post_file'] && this.$refs['post_file'].files.length > 0)) {
|
||||
this.disableFileUpload = false;
|
||||
this.oldDomain = '';
|
||||
return;
|
||||
}
|
||||
this.disableFileUpload = true;
|
||||
var domain = $j("<a>").prop("href", this.uploadURL).prop("hostname");
|
||||
|
||||
if (domain && domain != this.oldDomain) {
|
||||
$j.getJSON("/upload_whitelist/is_whitelisted", {url: this.uploadURL}, function (data) {
|
||||
if (data.domain)
|
||||
self.whitelistWarning(data.is_whitelisted, data.domain);
|
||||
});
|
||||
}
|
||||
this.oldDomain = domain;
|
||||
|
||||
var src = thumbs.none;
|
||||
if (this.uploadURL.match(/^(https?\:\/\/|www).*?\.(jpg|jpeg|gif|png)/))
|
||||
src = this.uploadURL;
|
||||
else if (this.uploadURL.match(/^(https?\:\/\/|www).*?\.(swf)$/))
|
||||
src = thumbs.flash;
|
||||
else if (this.uploadURL.match(/^(https?\:\/\/|www).*?\.(webm)$/))
|
||||
src = thumbs.webm;
|
||||
|
||||
this.previewURL = src;
|
||||
}
|
||||
|
||||
function updateDimensionTag() {
|
||||
var self = this;
|
||||
if (!(self.previewHeight || self.previewWidth))
|
||||
return;
|
||||
var otherTags = ['low_res', 'hi_res', 'superabsurd_res', 'absurd_res'];
|
||||
var ourTag = function (h, w) {
|
||||
if (!(h && w)) {
|
||||
return null;
|
||||
}
|
||||
if ((h <= 500) && (w <= 500))
|
||||
return 'low_res';
|
||||
if ((h >= 10000) || (w >= 10000))
|
||||
return 'superabsurb_res';
|
||||
if ((h >= 2400) || (w >= 3200))
|
||||
return 'absurd_res';
|
||||
if ((h >= 1200) || (w >= 1600))
|
||||
return 'hi_res';
|
||||
return null;
|
||||
}(self.previewHeight, self.previewWidth);
|
||||
var tagIdx = otherTags.indexOf(ourTag);
|
||||
if (tagIdx > 0)
|
||||
otherTags.splice(tagIdx, 1);
|
||||
if (ourTag)
|
||||
self.pushTag(ourTag, true);
|
||||
for(var i = 0; i < otherTags.length; i++) {
|
||||
self.pushTag(otherTags[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreview() {
|
||||
if (this.$refs['post_file'] && this.$refs['post_file'].files[0])
|
||||
updatePreviewFile.call(this);
|
||||
else
|
||||
updatePreviewURL.call(this);
|
||||
}
|
||||
|
||||
function directURLCheck(url) {
|
||||
var patterns = [{reason: 'Thumbnail URL', test: /[at]\.facdn\.net/gi},
|
||||
{reason: 'Sample URL', test: /pximg\.net\/img-master/gi},
|
||||
// {reason: 'Sample URL', test: /\d+\.media\.tumblr\.com/gi}, // Tumblr broke raws.
|
||||
{reason: 'Sample URL', test: /d3gz42uwgl1r1y\.cloudfront\.net\/.*\/\d+x\d+\./gi},
|
||||
{reason: 'Sample URL', test: /pbs\.twimg\.com\/media\/\w+\.(jpg|png)(:large)?$/gi},
|
||||
{reason: 'Sample URL', test: /pbs\.twimg\.com\/media\/\w+\?format=/gi},
|
||||
{reason: 'Sample URL', test: /derpicdn\.net\/.*\/large\./gi},
|
||||
{reason: 'Sample URL', test: /metapix\.net\/files\/(preview|screen)\//gi},
|
||||
{reason: 'Sample URL', test: /sofurryfiles\.com\/std\/preview/gi}];
|
||||
for (var i = 0; i < patterns.length; ++i) {
|
||||
var pattern = patterns[i];
|
||||
if (pattern.test.test(url))
|
||||
return pattern.reason;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function clearFileUpload() {
|
||||
if (!(this.$refs['post_file'] && this.$refs['post_file'].files[0]))
|
||||
return;
|
||||
this.$refs['post_file'].value = null;
|
||||
this.disableURLUpload = this.disableFileUpload = false;
|
||||
this.previewURL = thumbs.none;
|
||||
this.previewHeight = this.previewWidth = 0;
|
||||
this.updatePreview();
|
||||
}
|
||||
|
||||
var source = Vue.extend({
|
||||
template: '#source-template',
|
||||
props: ['value', 'index', 'last'],
|
||||
data: function () {
|
||||
return {
|
||||
backendValue: this.value
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
'realValue': {
|
||||
get: function () {
|
||||
return this.backendValue;
|
||||
},
|
||||
set: function (v) {
|
||||
this.backendValue = v;
|
||||
this.$emit('input', v);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add: function () {
|
||||
this.$emit('add');
|
||||
},
|
||||
remove: function () {
|
||||
this.$emit('delete');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: function (v) {
|
||||
this.backendValue = v;
|
||||
}
|
||||
}
|
||||
});
|
||||
var checkbox = Vue.extend({
|
||||
template: '#checkbox-template',
|
||||
props: ['check', 'checks'],
|
||||
computed: {
|
||||
value: {
|
||||
get: function () {
|
||||
if (this.checks[this.tagName] === undefined)
|
||||
return false;
|
||||
return this.checks[this.tagName];
|
||||
},
|
||||
set: function (v) {
|
||||
this.$emit('set', this.tagName, v);
|
||||
}
|
||||
},
|
||||
tagName: function () {
|
||||
return this.check.tag || this.check.name.toLowerCase().replace(/ /g, '_');
|
||||
}
|
||||
}
|
||||
});
|
||||
var tagSorter = function (a, b) {
|
||||
return a[0] > b[0] ? 1 : -1;
|
||||
};
|
||||
var relatedTags = Vue.extend({
|
||||
template: '#related-tags',
|
||||
props: ['tags', 'related', 'loading'],
|
||||
data: function () {
|
||||
return {
|
||||
uploaded: (userUploadTags || []),
|
||||
recent: (userRecentTags || []).sort(tagSorter),
|
||||
artists: (userArtistTags || []).sort(tagSorter)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggle: function (tag) {
|
||||
this.$emit('tag-active', tag[0], !this.tagActive(tag));
|
||||
},
|
||||
tagLink: function (tag) {
|
||||
return '/post/index?tags=' + encodeURIComponent(tag[0]);
|
||||
},
|
||||
tagActive: function (tag) {
|
||||
return this.tags.indexOf(tag[0]) !== -1;
|
||||
},
|
||||
tagClasses: function (tag) {
|
||||
var classes = {'tag-active': this.tagActive(tag)};
|
||||
classes['tag-type-' + tag[2]] = true;
|
||||
return classes;
|
||||
},
|
||||
splitTags: function (tags) {
|
||||
var chunkArray = function (arr, size) {
|
||||
var chunks = [];
|
||||
for (var i = 0; i < arr.length; i += size) {
|
||||
chunks.push(arr.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
return chunkArray(tags, 15);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tagGroups: {
|
||||
get: function () {
|
||||
var groups = [];
|
||||
if (this.uploaded && this.uploaded.length) {
|
||||
groups.push({
|
||||
title: "Quick Tags",
|
||||
tags: this.uploaded
|
||||
});
|
||||
}
|
||||
if (this.recent && this.recent.length) {
|
||||
groups.push({
|
||||
title: "Recent",
|
||||
tags: this.recent
|
||||
});
|
||||
}
|
||||
if (this.artists && this.artists.length) {
|
||||
groups.push({
|
||||
title: "Artists",
|
||||
tags: this.artists
|
||||
});
|
||||
}
|
||||
if (this.related && this.related.length) {
|
||||
for (var i = 0; i < this.related.length; i++) {
|
||||
groups.push(this.related[i]);
|
||||
}
|
||||
}
|
||||
if (this.loading) {
|
||||
groups.push({title: 'Loading Related Tags', tags: [['', '', '']]});
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
var tagPreviewTag = Vue.extend({
|
||||
functional: true,
|
||||
props: ['tag'],
|
||||
render: function (h, ctx) {
|
||||
var tag = ctx.props.tag;
|
||||
switch (tag.type) {
|
||||
default:
|
||||
case 'tag':
|
||||
return h('a', {
|
||||
staticClass: 'tag-preview tag-type-' + tag.tagType
|
||||
}, tag.a);
|
||||
case 'alias':
|
||||
return h('span', {staticClass: 'tag-preview tag-preview-alias'}, [
|
||||
h('del', undefined, [
|
||||
h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.a)
|
||||
]), ' → ', h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.b)
|
||||
]);
|
||||
case 'implication':
|
||||
return h('span', {staticClass: 'tag-preview tag-preview-implication'}, [
|
||||
h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.a), ' ⇐ ', h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.b)
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
var tagPreview = Vue.extend({
|
||||
template: '#tag-preview',
|
||||
props: ['tags', 'loading'],
|
||||
components: {
|
||||
'tag-preview': tagPreviewTag
|
||||
},
|
||||
methods: {
|
||||
close: function () {
|
||||
this.$emit('close');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
splitTags: function () {
|
||||
var newTags = this.tags.concat([]);
|
||||
newTags.sort(function (a, b) {
|
||||
return a.a === b.a ? 0 : (a.a < b.a ? -1 : 1);
|
||||
});
|
||||
var chunkArray = function (arr, size) {
|
||||
var chunks = [];
|
||||
for (var i = 0; i < arr.length; i += size) {
|
||||
chunks.push(arr.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
return chunkArray(newTags, 15);
|
||||
}
|
||||
}
|
||||
});
|
||||
var editor = Vue.extend({
|
||||
template: '#post-creator',
|
||||
props: ['safe'],
|
||||
components: {
|
||||
'image-source': source,
|
||||
'image-checkbox': checkbox,
|
||||
'related-tags': relatedTags,
|
||||
'tag-preview': tagPreview
|
||||
},
|
||||
data: function () {
|
||||
var allChecks = {};
|
||||
var addChecks = function (check) {
|
||||
if (typeof check['tag'] !== "undefined") {
|
||||
allChecks[check.tag] = true;
|
||||
return
|
||||
}
|
||||
allChecks[check.name.toLowerCase().replace(' ', '_')] = true;
|
||||
};
|
||||
artist_checks.forEach(addChecks);
|
||||
sex_checks.forEach(addChecks);
|
||||
pairing_checks.forEach(addChecks);
|
||||
char_count_checks.forEach(addChecks);
|
||||
body_type_checks.forEach(addChecks);
|
||||
return {
|
||||
showErrors: false,
|
||||
whitelist: {
|
||||
visible: false,
|
||||
allowed: false,
|
||||
domain: ''
|
||||
},
|
||||
submitting: false,
|
||||
disableFileUpload: false,
|
||||
disableURLUpload: false,
|
||||
|
||||
previewHeight: 0,
|
||||
previewWidth: 0,
|
||||
overDims: false,
|
||||
uploadURL: '',
|
||||
previewURL: thumbs.none,
|
||||
|
||||
oldDomain: '',
|
||||
|
||||
noSource: false,
|
||||
sources: [''],
|
||||
normalMode: true,
|
||||
|
||||
checkboxes: {
|
||||
artist: artist_checks,
|
||||
sex: sex_checks,
|
||||
pairing: pairing_checks,
|
||||
count: char_count_checks,
|
||||
body: body_type_checks,
|
||||
selected: {},
|
||||
all: allChecks
|
||||
},
|
||||
tagEntries: {
|
||||
character: '',
|
||||
sex: '',
|
||||
bodyType: '',
|
||||
theme: '',
|
||||
other: ''
|
||||
},
|
||||
|
||||
preview: {
|
||||
loading: false,
|
||||
show: false,
|
||||
tags: []
|
||||
},
|
||||
|
||||
relatedTags: [],
|
||||
loadingRelated: false,
|
||||
|
||||
parentID: '',
|
||||
description: '',
|
||||
rating: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updatePreview: updatePreview,
|
||||
updatePreviewDims: updatePreviewDims,
|
||||
previewError: previewError,
|
||||
clearFile: clearFileUpload,
|
||||
whitelistWarning: function (allowed, domain) {
|
||||
this.whitelist.allowed = allowed;
|
||||
this.whitelist.domain = domain;
|
||||
this.whitelist.visible = true;
|
||||
},
|
||||
removeSource: function (i) {
|
||||
this.sources.splice(i, 1);
|
||||
},
|
||||
addSource: function () {
|
||||
if (this.sources.length < 5)
|
||||
this.sources.push('');
|
||||
},
|
||||
setCheck: function (tag, value) {
|
||||
Vue.set(this.checkboxes.selected, tag, value);
|
||||
},
|
||||
submit: function () {
|
||||
this.showErrors = true;
|
||||
if (this.preventUpload || this.submitting)
|
||||
return;
|
||||
var self = this;
|
||||
this.submitting = true;
|
||||
var data = new FormData();
|
||||
var post_file = this.$refs['post_file'];
|
||||
if (post_file && post_file.files && post_file.files.length) {
|
||||
data.append('upload[file]', this.$refs['post_file'].files[0]);
|
||||
} else {
|
||||
data.append('upload[direct_url]', this.uploadURL);
|
||||
}
|
||||
data.append('upload[tag_string]', this.tags);
|
||||
data.append('upload[rating]', this.rating);
|
||||
data.append('upload[source]', this.sources.join('\n'));
|
||||
// data.append('upload[description]', this.description);
|
||||
data.append('upload[parent_id]', this.parentID);
|
||||
jQuery.ajax('/uploads.json', {
|
||||
contentType: false,
|
||||
processData: false,
|
||||
method: 'POST',
|
||||
type: 'POST',
|
||||
data: data,
|
||||
success: function (data) {
|
||||
self.submitting = false;
|
||||
notice('Post uploaded successfully.');
|
||||
location.assign(data.location);
|
||||
},
|
||||
error: function (data) {
|
||||
self.submitting = false;
|
||||
try {
|
||||
var data2 = JSON.parse(data.responseText);
|
||||
if (data2 && data2.location) {
|
||||
error('Error: Post already exists. <a href="' + data2.location + '">View it.</a>');
|
||||
} else {
|
||||
error('Error: ' + data2.reason);
|
||||
}
|
||||
} catch (e) {
|
||||
error('Error: Unknown error! ' + data);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
pushTag: function (tag, add) {
|
||||
this.preview.show = false;
|
||||
var isCheck = typeof this.checkboxes.all[tag] !== "undefined";
|
||||
// In advanced mode we need to push these into the tags area because there are no checkboxes or other
|
||||
// tag fields so we can't see them otherwise.
|
||||
if (isCheck && this.normalMode) {
|
||||
this.setCheck(tag, add);
|
||||
return;
|
||||
}
|
||||
var tags = this.tagEntries.other ? this.tagEntries.other.trim().split(' ') : [];
|
||||
var tagIdx = tags.indexOf(tag);
|
||||
if (add) {
|
||||
if (tagIdx === -1)
|
||||
tags.push(tag);
|
||||
} else {
|
||||
if (tagIdx === -1)
|
||||
return;
|
||||
tags.splice(tagIdx, 1);
|
||||
}
|
||||
this.tagEntries.other = tags.join(' ') + ' ';
|
||||
},
|
||||
previewFinalTags: function () {
|
||||
if (this.preview.loading)
|
||||
return;
|
||||
if (this.preview.show) {
|
||||
this.preview.show = false;
|
||||
return;
|
||||
}
|
||||
this.preview.loading = true;
|
||||
this.preview.show = true;
|
||||
this.preview.tags = [];
|
||||
var self = this;
|
||||
var data = {tags: this.tags};
|
||||
jQuery.ajax("/tags/preview.json", {
|
||||
method: 'POST',
|
||||
type: 'POST',
|
||||
data: data,
|
||||
success: function (result) {
|
||||
self.preview.loading = false;
|
||||
self.preview.tags = result;
|
||||
},
|
||||
error: function (result) {
|
||||
self.preview.loading = false;
|
||||
self.preview.tags = [];
|
||||
self.preview.show = false;
|
||||
error('Error loading tag preview ' + result);
|
||||
}
|
||||
})
|
||||
},
|
||||
findRelated: function (type) {
|
||||
const self = this;
|
||||
const convertResponse = function (respData) {
|
||||
var sortedRelated = [];
|
||||
for (var key in respData) {
|
||||
if (!respData.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
sortedRelated.push({title: 'Related: ' + key, tags: respData[key].sort(tagSorter)});
|
||||
}
|
||||
return sortedRelated;
|
||||
};
|
||||
const getSelectedTags = function () {
|
||||
const field = self.$refs['otherTags'];
|
||||
if (!field.hasOwnProperty('selectionStart'))
|
||||
return null;
|
||||
const length = field.selectionEnd - field.selectionStart;
|
||||
if (length)
|
||||
return field.value.substr(field.selectionStart, length);
|
||||
return null;
|
||||
};
|
||||
this.loadingRelated = true;
|
||||
this.relatedTags = [];
|
||||
const selectedTags = getSelectedTags();
|
||||
const params = selectedTags ? {query: selectedTags} : {query: this.tags};
|
||||
|
||||
if (type)
|
||||
params['category'] = type;
|
||||
$.getJSON("/related_tag/bulk.json", params, function(data) {
|
||||
self.relatedTags = convertResponse(data);
|
||||
}).always(function() {
|
||||
self.loadingRelated = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tags: function () {
|
||||
var self = this;
|
||||
var checked = Object.keys(this.checkboxes.selected).filter(function (x) {
|
||||
return self.checkboxes.selected[x] === true;
|
||||
});
|
||||
return checked.concat([this.tagEntries.other, this.tagEntries.sex, this.tagEntries.bodyType,
|
||||
this.tagEntries.theme, this.tagEntries.character]).join(' ').replace(',', ' ').trim().replace(/ +/g, ' ');
|
||||
},
|
||||
tagsArray: function () {
|
||||
return this.tags.toLowerCase().split(' ');
|
||||
},
|
||||
previewDimensions: function () {
|
||||
if (this.previewWidth && this.previewHeight)
|
||||
return this.previewHeight + '×' + this.previewWidth;
|
||||
return '';
|
||||
},
|
||||
directURLProblem: function () {
|
||||
return directURLCheck(this.uploadURL);
|
||||
},
|
||||
badDirectURL: function () {
|
||||
return !!this.directURLProblem;
|
||||
},
|
||||
sourceWarning: function () {
|
||||
var validSourceCount = this.sources.filter(function (i) {
|
||||
return i.length > 0;
|
||||
}).length;
|
||||
return !this.noSource && (validSourceCount === 0);
|
||||
},
|
||||
tagCount: function () {
|
||||
return this.tags.split(' ').filter(function (x) {
|
||||
return x;
|
||||
}).length;
|
||||
},
|
||||
notEnoughTags: function () {
|
||||
return this.tagCount < 4;
|
||||
},
|
||||
invalidRating: function () {
|
||||
return !this.rating;
|
||||
},
|
||||
preventUpload: function () {
|
||||
return this.sourceWarning || this.badDirectURL || this.notEnoughTags
|
||||
|| this.invalidRating;
|
||||
}
|
||||
}
|
||||
});
|
||||
var main = new Vue({
|
||||
el: '#post-add',
|
||||
components: {
|
||||
'post-creator': editor
|
||||
}
|
||||
});
|
||||
Danbooru.Uploader.init();
|
||||
</script>
|
||||
|
@ -1,178 +0,0 @@
|
||||
<style>
|
||||
.toggle-button {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: center;
|
||||
user-select: none;
|
||||
padding: .175em .5rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
border-radius: 0.25rem;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.toggle-button.active {
|
||||
background-color: #FDF5D9;
|
||||
}
|
||||
|
||||
.flex-grid-outer {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.upload_preview_container.in-editor {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upload_preview_container.in-sidebar {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.flex-grid {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.col {
|
||||
flex: 1 0 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.col2 {
|
||||
flex: 2 1 0;
|
||||
}
|
||||
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.come-together-now {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.over-me {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.related-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 0 1 10%;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.related-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 -5px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.related-item {
|
||||
padding: 0 5px;
|
||||
max-width: 140px;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.related-title {
|
||||
padding: 0 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tag-active {
|
||||
background: rgb(0, 111, 250);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag-preview {
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
background: rgba(1, 1, 1, 0.15);
|
||||
border-radius: 2px;
|
||||
padding: 3px;
|
||||
margin-right: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tag-preview-alias {
|
||||
background-color: rgba(150, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.tag-preview-implication {
|
||||
background-color: rgba(0, 150, 0, 0.25);
|
||||
}
|
||||
|
||||
.tag-textarea {
|
||||
display: inline-block; /* Why were we even unsetting this? It breaks EVERYTHING. */
|
||||
font-size: 1rem;
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* Need to override this so it shows up at all. */
|
||||
#whitelist-warning {
|
||||
display: block;
|
||||
float: none;
|
||||
}
|
||||
|
||||
@media only screen and (orientation: portrait), (max-width: 1100px) {
|
||||
#preview-sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upload_preview_container.in-editor {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.upload_preview_container.in-sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upload_preview_dims {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.upload_preview_img {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.below-upload > .upload_preview_img {
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
.related-section {
|
||||
flex: 0 1 50%;
|
||||
}
|
||||
|
||||
.flex-grid {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.the_secret_switch {
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
@ -1,12 +0,0 @@
|
||||
<template id="related-tags">
|
||||
<div class="related-tags flex-wrap">
|
||||
<div class="related-section" v-for="group in tagGroups" :key="group.title">
|
||||
<div class="related-items" v-for="tags, i in splitTags(group.tags)" :key="i">
|
||||
<div class="related-title" v-if="i === 0">{{group.title}}</div>
|
||||
<div class="related-item" v-for="tag in tags" :key="tag[0]">
|
||||
<a :class="tagClasses(tag)" :href="tagLink(tag)" @click.prevent="toggle(tag)">{{tag[0]}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,7 +0,0 @@
|
||||
<template id="source-template">
|
||||
<div>
|
||||
<input type="text" size="50" v-model="realValue" @keyup.enter="add"/>
|
||||
<button @click="remove" v-if="index !== 0">-</button>
|
||||
<button @click="add" v-if="last && index < 4">+</button>
|
||||
</div>
|
||||
</template>
|
@ -1,13 +0,0 @@
|
||||
<template id="tag-preview">
|
||||
<div>
|
||||
<div v-show="loading">Fetching tags...</div>
|
||||
<div class="related-tags flex-wrap">
|
||||
<div class="related-items" v-for="sTags, i in splitTags" :key="i">
|
||||
<tag-preview v-for="tag, $idx in sTags" :key="$idx" :tag="tag"></tag-preview>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#" @click.prevent="close">Close Preview</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,228 +0,0 @@
|
||||
<template id="checkbox-template">
|
||||
<button class="toggle-button" :class="{active: value}" @click="value = !value">{{check.name}}</button>
|
||||
</template>
|
||||
<template id="post-creator">
|
||||
<div class="flex-grid-outer">
|
||||
<div class="col box-section" style="flex: 2 0 0;">
|
||||
<div class="the_secret_switch" @click="normalMode = !normalMode"></div>
|
||||
<% unless @upload.new_record? %>
|
||||
<div class="box-section" style="margin-bottom: 1rem;">
|
||||
<p>This post was already uploaded
|
||||
(<%= link_to "post ##{@upload.id}", {action: "show", id: @upload.id}, target: "_blank" %>).</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_file">Image</label>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div v-if="!disableFileUpload">
|
||||
<label>File:
|
||||
<input type="file" ref="post_file" @change="updatePreview" :disabled="disableFileUpload"/>
|
||||
</label>
|
||||
<button @click="clearFile" v-show="disableURLUpload">Clear</button>
|
||||
</div>
|
||||
<div v-if="!disableURLUpload">
|
||||
<div class="box-section sect_red" v-if="badDirectURL">
|
||||
The direct URL entered has the following problem: {{ directURLProblem }}<br>
|
||||
You should review <a href="/wiki/show/howto:sites_and_sources">the sourcing guide</a>.
|
||||
</div>
|
||||
<label>{{!disableFileUpload ? '(or) ' : '' }}URL:
|
||||
<input type="text" size="50" v-model="uploadURL" @keyup="updatePreview" :disabled="disableURLUpload"/>
|
||||
</label>
|
||||
<div id="whitelist-warning" v-show="whitelist.visible" :class="{'whitelist-warning-allowed': whitelist.allowed, 'whitelist-warning-disallowed': !whitelist.allowed}">
|
||||
<span v-if="whitelist.allowed">Uploads from <b>{{whitelist.domain}}</b> are permitted.</span>
|
||||
<span v-if="!whitelist.allowed">Uploads from <b>{{whitelist.domain}}</b> are not permitted. (<a href='/upload_whitelist'>View whitelisted domains</a>)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-section upload_preview_container in-editor below-upload">
|
||||
<div class="upload_preview_dims">{{ previewDimensions }}</div>
|
||||
<img class="upload_preview_img" :src="previewURL" style="max-width: 100%;"/>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_sources">Sources</label>
|
||||
<div>You should include: A link to the artists page where this was obtained, and a link to the submission page
|
||||
where this image was obtained. No available source should ONLY be used if the content has never been posted
|
||||
online anywhere else.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div class="box-section sect_red" v-show="showErrors && sourceWarning">A source must be provided or you must
|
||||
select that there
|
||||
is no available source.
|
||||
</div>
|
||||
<div v-if="!noSource">
|
||||
<image-source :last="i === (sources.length-1)" :index="i" v-model="sources[i]" v-for="s, i in sources"
|
||||
@delete="removeSource(i)" @add="addSource" :key="i"></image-source>
|
||||
</div>
|
||||
<div>
|
||||
<label class="section-label"><input type="checkbox" id="no_source" v-model="noSource"/> No available source
|
||||
/ I am the source.</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="normalMode">
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="names">Artists</label>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div>
|
||||
<textarea class="tag-textarea" v-model="tagEntries.character" id="post_characters" rows="2"
|
||||
placeholder="Ex: artist_name etc."></textarea>
|
||||
</div>
|
||||
<div class="flex-wrap">
|
||||
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.artist" @set="setCheck"
|
||||
:key="check.name"></image-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_sex_tags">Characters</label>
|
||||
<div>Select (and write in) all that apply. Character sex is based only on what is visible in the image.
|
||||
Outside information or other images should not be used when deciding what tags are used.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div class="flex-wrap">
|
||||
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.sex" @set="setCheck"
|
||||
:key="check.name"></image-checkbox>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="flex-wrap">
|
||||
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.count" @set="setCheck"
|
||||
:key="check.name"></image-checkbox>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="flex-wrap">
|
||||
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.pairing" @set="setCheck"
|
||||
:key="check.name"></image-checkbox>
|
||||
</div>
|
||||
<textarea class="tag-textarea" rows="2" v-model="tagEntries.sex" id="post_sexes"
|
||||
placeholder="Ex: character_name solo_focus etc."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label">Body Types and Species</label>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div class="flex-wrap">
|
||||
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.body" @set="setCheck"
|
||||
:key="check.name"></image-checkbox>
|
||||
</div>
|
||||
<textarea class="tag-textarea" rows="2" v-model="tagEntries.bodyType" id="post_bodyTypes"
|
||||
placeholder="Ex: bear dragon hyena rat newt etc."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label">Contentious Content</label>
|
||||
<div>
|
||||
These allow users to find or blacklist content with ease. Make sure that you are tagging these upon
|
||||
initial upload.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<textarea class="tag-textarea" v-model="tagEntries.theme" id="post_themes" rows="2"
|
||||
placeholder="Ex: cub young gore scat watersports diaper my_little_pony vore not_furry rape etc."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_rating_questionable">Rating</label>
|
||||
<div>Explicit tags include sex, pussy, penis, masturbation, fellatio, etc.
|
||||
(<%= link_to "help", help_pages_path(title: 'ratings'), target: "_blank" %>)
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div class="box-section sect_red" v-if="showErrors && invalidRating">
|
||||
You must select an appropriate rating for this image.
|
||||
</div>
|
||||
<div>
|
||||
<template v-if="!safe">
|
||||
<button class="toggle-button" :class="{active: rating==='e'}" @click="rating = 'e'">Explicit</button>
|
||||
<button class="toggle-button" :class="{active: rating==='q'}" @click="rating = 'q'">Questionable</button>
|
||||
</template>
|
||||
<button class="toggle-button" :class="{active: rating==='s'}" @click="rating = 's'">Safe</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid come-together-now">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_tags">Other Tags</label>
|
||||
<div>
|
||||
Separate tags with spaces. (<%= link_to "help", help_pages_path(title: 'tags'), target: "_blank" %>)
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div class="box-section upload_preview_container in-editor">
|
||||
<div class="upload_preview_dims">{{ previewDimensions }}</div>
|
||||
<img class="upload_preview_img" :src="previewURL" style="max-width: 100%;"/>
|
||||
</div>
|
||||
<div class="box-section sect_red" v-show="showErrors && notEnoughTags">
|
||||
You must provide at least <b>{{4 - tagCount}}</b> more tags. Tags in other sections count towards this
|
||||
total.
|
||||
</div>
|
||||
<div v-show="!preview.show">
|
||||
<textarea class="tag-textarea" id="post_tags" v-model="tagEntries.other" rows="5" ref="otherTags"></textarea>
|
||||
</div>
|
||||
<div v-show="preview.show">
|
||||
<tag-preview :tags="preview.tags" :loading="preview.loading" @close="previewFinalTags"></tag-preview>
|
||||
</div>
|
||||
<div class="related-tag-functions">
|
||||
<a href="#" @click.prevent="findRelated()">Related Tags</a> |
|
||||
<a href="#" @click.prevent="findRelated('artist')">Related Artists</a> |
|
||||
<a href="#" @click.prevent="findRelated('char')">Related Characters</a> |
|
||||
<a href="#" @click.prevent="findRelated('copyright')">Related Copyrights</a> |
|
||||
<a href="#" @click.prevent="previewFinalTags">Preview Final Tags</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom over-me">
|
||||
<related-tags :tags="tagsArray" :related="relatedTags" :loading="loadingRelated" @tag-active="pushTag"></related-tags>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label">Parent Post ID</label>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<input type="number" v-model.number="parentID" placeholder="Ex. 12345"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_description">Description</label>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<textarea class="tag-textarea" id="post_description" v-model="description" rows="5"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grid">
|
||||
<div class="col"></div>
|
||||
<div class="col2">
|
||||
<div class="box-section sect_red" v-show="preventUpload && showErrors">
|
||||
Unmet requirements above prevent the submission of the post.
|
||||
</div>
|
||||
<div class="box-section sect_green" v-show="submitting">
|
||||
Submitting your post, please wait.
|
||||
</div>
|
||||
<button @click="submit" :disabled="(showErrors && preventUpload) || submitting" accesskey="s">{{ submitting ? 'Uploading...' :
|
||||
'Upload' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="preview-sidebar" class="col box-section" style="margin-left: 10px; padding: 10px;">
|
||||
<div class="upload_preview_container in-sidebar">
|
||||
<div class="upload_preview_dims">{{ previewDimensions }}</div>
|
||||
<img class="upload_preview_img" :src="previewURL" style="max-width: 100%;" @load="updatePreviewDims" @error="previewError"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,5 +1,6 @@
|
||||
const { environment } = require('@rails/webpacker')
|
||||
const erb = require('./loaders/erb')
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||
const webpack = require('webpack');
|
||||
|
||||
environment.loaders.append('scss.erb', {
|
||||
@ -14,6 +15,12 @@ environment.loaders.append('scss.erb', {
|
||||
]
|
||||
});
|
||||
|
||||
environment.loaders.append('vue', {
|
||||
test: /\.vue$/,
|
||||
use: 'vue-loader'
|
||||
});
|
||||
environment.plugins.append('VueLoaderPlugin', new VueLoaderPlugin());
|
||||
|
||||
environment.loaders.append('erb', erb);
|
||||
|
||||
environment.config.output.library = ["Danbooru"];
|
||||
|
@ -10,8 +10,10 @@
|
||||
"qtip2": "^3.0.3",
|
||||
"rails-erb-loader": "^5.4.2",
|
||||
"script-loader": "^0.7.2",
|
||||
"webpack-cli": "^3.0.8",
|
||||
"vue": "^2.6.10"
|
||||
"vue": "^2.6.10",
|
||||
"vue-loader": "^15.7.0",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"webpack-cli": "^3.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^5.3.0",
|
||||
|
138
yarn.lock
138
yarn.lock
@ -31,6 +31,21 @@
|
||||
webpack "^3.11.0"
|
||||
webpack-manifest-plugin "^1.3.2"
|
||||
|
||||
"@vue/component-compiler-utils@^2.5.1":
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz#aa46d2a6f7647440b0b8932434d22f12371e543b"
|
||||
integrity sha512-IHjxt7LsOFYc0DkTncB7OXJL7UzwOLPPQCfEUNyxL2qt+tF12THV+EO33O1G2Uk4feMSWua3iD39Itszx0f0bw==
|
||||
dependencies:
|
||||
consolidate "^0.15.1"
|
||||
hash-sum "^1.0.2"
|
||||
lru-cache "^4.1.2"
|
||||
merge-source-map "^1.1.0"
|
||||
postcss "^7.0.14"
|
||||
postcss-selector-parser "^5.0.0"
|
||||
prettier "1.16.3"
|
||||
source-map "~0.6.1"
|
||||
vue-template-es2015-compiler "^1.9.0"
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
@ -877,6 +892,11 @@ block-stream@*:
|
||||
dependencies:
|
||||
inherits "~2.0.0"
|
||||
|
||||
bluebird@^3.1.1:
|
||||
version "3.5.5"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
|
||||
integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
|
||||
|
||||
bluebird@^3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
|
||||
@ -1183,6 +1203,15 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chardet@^0.4.0:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
|
||||
@ -1453,6 +1482,13 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
|
||||
|
||||
consolidate@^0.15.1:
|
||||
version "0.15.1"
|
||||
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
|
||||
integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==
|
||||
dependencies:
|
||||
bluebird "^3.1.1"
|
||||
|
||||
constants-browserify@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
|
||||
@ -1640,6 +1676,11 @@ cssesc@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
|
||||
|
||||
cssesc@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703"
|
||||
integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==
|
||||
|
||||
cssnano@^3.10.0:
|
||||
version "3.10.0"
|
||||
resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38"
|
||||
@ -1711,6 +1752,11 @@ date-now@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||
|
||||
de-indent@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
|
||||
|
||||
debug-loader@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/debug-loader/-/debug-loader-0.0.1.tgz#44dc37e09e3c39e6af334681960f70a534a9d056"
|
||||
@ -2901,6 +2947,11 @@ hash-base@^3.0.0:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
hash-sum@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
|
||||
integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=
|
||||
|
||||
hash.js@^1.0.0, hash.js@^1.0.3:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.4.tgz#8b50e1f35d51bd01e5ed9ece4dbe3549ccfa0a3c"
|
||||
@ -2917,6 +2968,11 @@ hawk@~3.1.3:
|
||||
hoek "2.x.x"
|
||||
sntp "1.x.x"
|
||||
|
||||
he@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hmac-drbg@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
@ -3787,6 +3843,14 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
|
||||
pseudomap "^1.0.2"
|
||||
yallist "^2.1.2"
|
||||
|
||||
lru-cache@^4.1.2:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
|
||||
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
|
||||
dependencies:
|
||||
pseudomap "^1.0.2"
|
||||
yallist "^2.1.2"
|
||||
|
||||
make-dir@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||
@ -3875,6 +3939,13 @@ merge-descriptors@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||
|
||||
merge-source-map@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
|
||||
integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
|
||||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
@ -5134,6 +5205,15 @@ postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2, postcss-selector
|
||||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
|
||||
postcss-selector-parser@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c"
|
||||
integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==
|
||||
dependencies:
|
||||
cssesc "^2.0.0"
|
||||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
|
||||
postcss-svgo@^2.1.1:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d"
|
||||
@ -5188,6 +5268,15 @@ postcss@^6.0, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.14,
|
||||
source-map "^0.6.1"
|
||||
supports-color "^5.4.0"
|
||||
|
||||
postcss@^7.0.14:
|
||||
version "7.0.17"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f"
|
||||
integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
source-map "^0.6.1"
|
||||
supports-color "^6.1.0"
|
||||
|
||||
prelude-ls@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
@ -5196,6 +5285,11 @@ prepend-http@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||
|
||||
prettier@1.16.3:
|
||||
version "1.16.3"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d"
|
||||
integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==
|
||||
|
||||
private@^0.1.6, private@^0.1.8:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
|
||||
@ -6312,6 +6406,13 @@ supports-color@^5.3.0, supports-color@^5.4.0:
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
|
||||
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
svgo@^0.7.0:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
|
||||
@ -6682,6 +6783,43 @@ vm-browserify@0.0.4:
|
||||
dependencies:
|
||||
indexof "0.0.1"
|
||||
|
||||
vue-hot-reload-api@^2.3.0:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz#2756f46cb3258054c5f4723de8ae7e87302a1ccf"
|
||||
integrity sha512-KmvZVtmM26BQOMK1rwUZsrqxEGeKiYSZGA7SNWE6uExx8UX/cj9hq2MRV/wWC3Cq6AoeDGk57rL9YMFRel/q+g==
|
||||
|
||||
vue-loader@^15.7.0:
|
||||
version "15.7.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.0.tgz#27275aa5a3ef4958c5379c006dd1436ad04b25b3"
|
||||
integrity sha512-x+NZ4RIthQOxcFclEcs8sXGEWqnZHodL2J9Vq+hUz+TDZzBaDIh1j3d9M2IUlTjtrHTZy4uMuRdTi8BGws7jLA==
|
||||
dependencies:
|
||||
"@vue/component-compiler-utils" "^2.5.1"
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.1.0"
|
||||
vue-hot-reload-api "^2.3.0"
|
||||
vue-style-loader "^4.1.0"
|
||||
|
||||
vue-style-loader@^4.1.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
|
||||
integrity sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ==
|
||||
dependencies:
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
vue-template-compiler@^2.6.10:
|
||||
version "2.6.10"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
|
||||
integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
|
||||
dependencies:
|
||||
de-indent "^1.0.2"
|
||||
he "^1.1.0"
|
||||
|
||||
vue-template-es2015-compiler@^1.9.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
||||
|
||||
vue@^2.6.10:
|
||||
version "2.6.10"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
|
||||
|
Loading…
Reference in New Issue
Block a user