Partial of new uploader

This commit is contained in:
Kira 2019-05-05 08:49:13 -07:00
parent f29e90f508
commit ae77880b10
12 changed files with 13086 additions and 159 deletions

View File

@ -59,7 +59,7 @@ class UploadsController < ApplicationController
def upload_params
permitted_params = %i[
file direct_url source tag_string rating status parent_id artist_commentary_title
file direct_url source tag_string rating parent_id description artist_commentary_title
artist_commentary_desc include_artist_commentary referer_url
md5_confirmation as_pending
]

View File

@ -1,153 +1,692 @@
<div id="c-uploads">
<div id="a-new">
<h1>Upload</h1>
<% if CurrentUser.can_upload? %>
<div id="upload-guide-notice">
<%= format_text(@upload_notice_wiki.try(&:body)) %>
<%= 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
the <%= link_to "how to upload guide", wiki_pages_path(id: "howto:upload") %>.</h2>
<p>Make sure you're not posting something on
the <%= link_to "Avoid Posting List", help_pages_path(id: 'avoid_posting') %><br>
Review the <%= link_to "Uploading Guidelines", help_pages_path(id: "uploading_guidelines") %>
<br>
Unsure what to tag your post
with? <%= link_to "Tagging Checklist", help_pages_path(id: "tagging_checklist") %></p>
</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>-->
<!-- You currently have <span class="post-uploads-remaining-count"><%#= current_user.upload_limit %></span> upload<%#= current_user.upload_limit!=1?"s":"" %> remaining.-->
<% unless CurrentUser.can_upload_free? %>
<p>Upload limit: <strong><%= CurrentUser.user.presenter.upload_limit(self) %></strong>.</p>
<% end %>
<%= render "image" %>
<%= render "related_posts", source: @source %>
<%= render "sources/info" %>
<div id="client-errors" class="error-messages ui-state-error ui-corner-all" style="display:none"></div>
<%= form_for(@upload, :html => {:multipart => true, :class => "simple_form", :id => "form"}) do |f| %>
<%= f.hidden_field :md5_confirmation %>
<%= f.hidden_field :referer_url, value: params[:ref] %>
<% if CurrentUser.can_upload_free? %>
<div class="input">
<label for="upload_as_pending">
<%= f.check_box :as_pending, :checked => params[:as_pending].present? %>
Upload for approval
</label>
<%# if current_user.pending_flagged_posts.count > 0 %>
<!-- This number will go up as some of your <%#= link_to "<span class='post-uploads-remaining-count'>#{current_user.pending_flagged_posts.count}</span> pending/flagged posts", action: "index",tags: "user:#{current_user.name} status:pending" %> are approved.-->
<%# end %>
<!-- </p>-->
<!-- You have <span class="post-uploads-remaining-count"><%#= current_user.post_upload_throttle %></span> uploads remaining this hour.-->
<!-- See <%#= link_to "here", controller: "user", action: "upload_limit" %> for more details.-->
<!-- </div>-->
<%# elsif current_user.post_upload_throttle <= 5 %>
<!-- <div id="post-uploads-remaining" class="section<%# if current_user.post_upload_throttle <= 0 %> sect_red<%# end %>" style="width:640px;">-->
<!-- You have <span class="post-uploads-remaining-count"><%#= current_user.post_upload_throttle %></span> uploads remaining this hour.-->
<!-- See <%#= link_to "here", controller: "user", action: "upload_limit" %> for more details.-->
<!-- </div>-->
<%# end %>
<post-creator :safe="<%= CurrentUser.safe_mode? %>"></post-creator>
</div>
<% end %>
<div class="input">
<%= f.label :file %>
<%= f.file_field :file, :size => 50 %>
</div>
<div class="input">
<%= f.label :direct_url %>
<%= f.text_field :direct_url, size: 50, value: params[:url] %>
<%= button_tag "Similar", :id => "similar-button", :type => "button", :class => "ui-button ui-widget ui-corner-all sub" %>
<div id="whitelist-warning"></div>
<span class="hint">You can enter a URL to have <%= Danbooru.config.app_name %> automatically download and process it</span>
</div>
<div class="input">
<%= f.label :source, "Sources" %>
<%= f.text_area :source, size: "60x5", value: params[:url] %>
</div>
<div class="input">
<%= f.label :rating %>
<fieldset class="ratings">
<%= f.radio_button :rating, :e, :checked => (params[:rating] == "e") %>
<%= f.label :rating_e, "Explicit", :title => "Hardcore porn, visible genitals" %>
<%= f.radio_button :rating, :q, :checked => (params[:rating] == "q") %>
<%= f.label :rating_q, "Questionable", :title => "Nudity, anything erotic" %>
<%= f.radio_button :rating, :s, :checked => (params[:rating] == "s") %>
<%= f.label :rating_s, "Safe", :title => "Everything else" %>
</fieldset>
</div>
<div class="input">
<%= f.label :parent_id, "Parent ID" %>
<%= f.text_field :parent_id, :value => params[:parent_id] %>
</div>
<div class="input">
<strong>Commentary</strong>
<a href="#" id="toggle-artist-commentary">show »</a>
<div class="artist-commentary" style="display: none;">
<div class="input">
<%= f.label :artist_commentary_title, "Title" %>
<%= f.text_field :artist_commentary_title, :value => params[:artist_commentary_title] %>
</div>
<div class="input">
<%= f.label :artist_commentary_desc, "Description" %>
<%= f.text_area :artist_commentary_desc, :size => "60x5", :value => params[:artist_commentary_desc] %>
</div>
<div class="input">
<label for="upload_include_artist_commentary">
<%= f.check_box :include_artist_commentary, :checked => params[:include_artist_commentary].present? %>
Include Commentary
</label>
</div>
</div>
</div>
<div class="input" id="tags-container">
<div class="header">
<%= f.label :tag_string, "Tags" %>
<span class="options">
<i id="face" class="fas"></i>
<span class="count"></span>
<a href="javascript:void(0)"><i id="open-edit-dialog" class="fas fa-arrows-alt" title="detach" data-shortcut="shift+e"></i></a>
</span>
</div>
<div>
<%= f.text_area :tag_string, :size => "60x5", :spellcheck => false, :"data-autocomplete" => "tag-edit", :"data-shortcut" => "e", :value => params[:tag_string] %>
</div>
<%= render "related_tags/buttons" %>
</div>
<div class="input">
<%= submit_tag "Submit", :id => "submit-button", :class => "large ui-button ui-widget ui-corner-all", data: { disable_with: false } %>
</div>
<%= render "related_tags/container" %>
<% end %>
<% else %>
<h2 style="margin-bottom: 1em;">You <%= CurrentUser.user.upload_limited_reason %></h2>
<% end %>
</div>
</div>
<% content_for(:page_title) do %>
Upload - <%= Danbooru.config.app_name %>
<% end %>
<% content_for(:html_header) do %>
<script async src="/vendor/spark-md5-3.0.min.js" integrity="sha384-5VJv9Bpdgn6Hi1U9X+oyrPZZ6vIihhKUi6Dc3DoCBGph3XbthcAH6va41dxo1KUO" crossorigin="anonymous"></script>
<!--<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>
$(function() {
var enabled = true;
var maxFilesize = <%= Danbooru.config.max_file_size.to_json %> / (1024 * 1024);
var userUploadTags = [];
var userRecentTags = [];
var userArtistTags = [];
</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'}];
if (!window.FileReader) {
enabled = false;
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);
}
// this.on("addedfile", function(file) {
// var reader = new FileReader();
// reader.addEventListener("loadend", function() {
// var buf = new SparkMD5.ArrayBuffer();
// buf.append(this.result);
// var hash = buf.end();
// $("#upload_md5_confirmation").val(hash);
// });
// reader.readAsArrayBuffer(file);
// });
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('post[file]', this.$refs['post_file'].files[0]);
} else {
data.append('post[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("/tag/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) {
var self = this;
var 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;
};
var getSelectedTags = function () {
var field = self.$refs['otherTags'];
if (!field.hasOwnProperty('selectionStart'))
return null;
var length = field.selectionEnd - field.selectionStart;
if (length)
return field.value.substr(field.selectionStart, length);
return null;
};
this.loadingRelated = true;
this.relatedTags = [];
var selectedTags = getSelectedTags();
var params = selectedTags ? {tags: selectedTags} : {tags: this.tags};
if (type)
params['type'] = type;
new Ajax.Request("/tag/related.json", {
method: 'POST',
parameters: params,
onComplete: function (resp) {
var respJ = resp.responseJSON;
self.relatedTags = convertResponse(respJ);
self.loadingRelated = false;
},
onError: 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
}
});
</script>
<% end %>
<%= render "uploads/secondary_links" %>

View File

@ -0,0 +1,178 @@
<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>

View File

@ -0,0 +1,12 @@
<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>

View File

@ -0,0 +1,7 @@
<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>

View File

@ -0,0 +1,13 @@
<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>

View File

@ -0,0 +1,228 @@
<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>

View File

@ -10,7 +10,8 @@
"qtip2": "^3.0.3",
"rails-erb-loader": "^5.4.2",
"script-loader": "^0.7.2",
"webpack-cli": "^3.0.8"
"webpack-cli": "^3.0.8",
"vue": "^2.6.10"
},
"devDependencies": {
"eslint": "^5.3.0",

11944
public/vendor/vue.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6682,6 +6682,11 @@ vm-browserify@0.0.4:
dependencies:
indexof "0.0.1"
vue@^2.6.10:
version "2.6.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
watchpack@^1.4.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"