diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 4b00f95c2..405431059 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -16,14 +16,10 @@ require("jquery-ui/ui/effects/effect-shake"); require("jquery-ui/ui/widgets/autocomplete"); require("jquery-ui/ui/widgets/button"); require("jquery-ui/ui/widgets/dialog"); -require("jquery-ui/ui/widgets/draggable"); -require("jquery-ui/ui/widgets/resizable"); require("jquery-ui/themes/base/core.css"); require("jquery-ui/themes/base/autocomplete.css"); require("jquery-ui/themes/base/button.css"); require("jquery-ui/themes/base/dialog.css"); -require("jquery-ui/themes/base/draggable.css"); -require("jquery-ui/themes/base/resizable.css"); require("jquery-ui/themes/base/theme.css"); require("@fortawesome/fontawesome-free/css/fontawesome.css"); diff --git a/app/javascript/src/javascripts/notes.js b/app/javascript/src/javascripts/notes.js index dac3d5cb3..f299a97fb 100644 --- a/app/javascript/src/javascripts/notes.js +++ b/app/javascript/src/javascripts/notes.js @@ -1,95 +1,150 @@ -import CurrentUser from './current_user' -import Utility from './utility' +import "jquery-ui/ui/widgets/draggable"; +import "jquery-ui/ui/widgets/resizable"; +import "jquery-ui/themes/base/draggable.css"; +import "jquery-ui/themes/base/resizable.css"; -let Note = { - HIDE_DELAY: 250, - NORMALIZE_ATTRIBUTES: ['letter-spacing', 'line-height', 'margin-left', 'margin-right', 'margin-top', 'margin-bottom', 'padding-left', 'padding-right', 'padding-top', 'padding-bottom'], - COPY_ATTRIBUTES: ['background-color', 'border-radius', 'transform', 'justify-content', 'align-items'], - permitted_style_values: function(attribute, $attribute_child) { - if ($attribute_child.length === 0) { - return ""; - } - let found_attribute = $attribute_child.attr('style').split(';').filter(val => val.match(RegExp(`(^| )${attribute}:`))); - if (found_attribute.length === 0) { - return ""; - } - let [, value] = found_attribute[0].trim().split(':').map(val => val.trim()); - if (attribute === "background-color") { - const color_code = $attribute_child.css('background-color'); - return color_code.startsWith('rgba') ? "" : value; - } - if (attribute === "transform") { - let rotate_match = value.match(/rotate\([^)]+\)/); - return rotate_match ? rotate_match[0] : ""; - } - return value; - }, +import CurrentUser from './current_user'; +import Utility, { clamp } from './utility'; - Box: { - create: function(id) { - var $inner_border = $('
'); - $inner_border.addClass("note-box-inner-border"); +class Note { + static HIDE_DELAY = 250; + static NORMALIZE_ATTRIBUTES = ['letter-spacing', 'line-height', 'margin-left', 'margin-right', 'margin-top', 'margin-bottom', 'padding-left', 'padding-right', 'padding-top', 'padding-bottom']; + static COPY_ATTRIBUTES = ['background-color', 'border-radius', 'transform', 'justify-content', 'align-items']; + static RESIZE_HANDLES = "se, nw"; - var $note_box = $('
'); - $note_box.addClass("note-box"); + // Notes must be at least 10x10 in size so they're big enough to drag and resize. + static MIN_NOTE_SIZE = 10; - if (Note.embed) { - $note_box.addClass("embedded"); + static notes = []; + static timeouts = []; + + id = null; + box = null; + body = null; + $note_container = null; + has_rotation = false; + + static Box = class { + note = null; + $note_box = null; + $inner_border = null; + + constructor(note) { + this.note = note; + this.$note_box = $('
'); + this.note.$note_container.append(this.$note_box); + + if (note.embed) { + this.$note_box.addClass("embedded"); + this.$inner_border = $('
'); + this.$note_box.append(this.$inner_border); } - $note_box.data("id", String(id)); - $note_box.attr("data-id", String(id)); - $note_box.draggable({ - containment: $("#image"), - stop: function(e, ui) { - Note.Box.update_data_attributes($note_box); - } + if (this.note.is_new()) { + this.$note_box.addClass("unsaved"); + } + + this.$note_box.draggable({ + containment: this.note.$note_container, }); - $note_box.resizable({ - containment: $("#image"), - handles: "se, nw", - stop: function(e, ui) { - Note.Box.update_data_attributes($note_box); - } + + this.$note_box.resizable({ + containment: this.note.$note_container, + handles: Note.RESIZE_HANDLES, + minWidth: Note.MIN_NOTE_SIZE, + minHeight: Note.MIN_NOTE_SIZE, }); - $note_box.css({position: "absolute"}); - $note_box.append($inner_border); - Note.Box.bind_events($note_box); - return $note_box; - }, + this.$note_box.on("click.danbooru", this.on_click.bind(this)); + this.$note_box.on("mouseenter.danbooru", this.on_mouseenter.bind(this)); + this.$note_box.on("mouseleave.danbooru", this.on_mouseleave.bind(this)); + this.$note_box.on("dragstart.danbooru resizestart.danbooru", this.on_dragstart.bind(this)); + this.$note_box.on("dragstop.danbooru resizestop.danbooru", this.on_dragstop.bind(this)); + } - update_data_attributes: function($note_box) { - var $image = $("#image"); - var ratio = $image.width() / parseFloat($image.data("original-width")); - var new_x = parseFloat($note_box.css("left")); - var new_y = parseFloat($note_box.css("top")); - var new_width = parseFloat($note_box.css("width")); - var new_height = parseFloat($note_box.css("height")); - new_x = parseInt(new_x / ratio); - new_y = parseInt(new_y / ratio); - new_width = parseInt(new_width / ratio); - new_height = parseInt(new_height / ratio); - $note_box.data("x", new_x); - $note_box.data("y", new_y); - $note_box.data("width", new_width); - $note_box.data("height", new_height); - }, + on_click() { + if (!Utility.test_max_width(660)) { + // Toggle selection status when note is clicked. Enable movement keys when note is selected. + if (this.$note_box.hasClass("movable")) { + this.$note_box.removeClass("movable"); + $(document).off("keydown.nudge_note"); + $(document).off("keydown.resize_note"); + } else { + this.$note_box.addClass("movable"); + Utility.keydown("up down left right", "nudge_note", this.key_nudge.bind(this)); + Utility.keydown("shift+up shift+down shift+left shift+right", "resize_note", this.key_resize.bind(this)); + } + } else if (this.$note_box.hasClass("viewing")) { + this.note.body.hide(); + this.$note_box.removeClass("viewing"); + } else { + $(".note-box").removeClass("viewing"); + this.note.body.show(); + this.$note_box.addClass("viewing"); + } + } - copy_style_attributes: function($note_box) { - const $attribute_child = $note_box.find('.note-box-attributes'); + on_mouseenter() { + this.note.body.show(); + this.$note_box.addClass("hovering"); + } + + on_mouseleave() { + this.note.body.hide(); + this.$note_box.removeClass("hovering"); + } + + on_dragstart() { + this.$note_box.addClass("unsaved"); + Note.Body.hide_all(); + } + + // Reset the note box placement after the box is dragged or resized. Dragging the note + // changes the CSS coordinates to pixels, so we have to convert them back to percentages. + on_dragstop() { + this.place_note(this.note.x, this.note.y, this.note.width, this.note.height); + } + + // Place the note box using absolute percentage coordinates (floats in the range 0.0..1.0). + place_note(x, y, w, h) { + if (this.note.embed && this.note.has_rotation) { + let position = this.get_min_max_position(); + x = position.norm_left / this.note.image_width; + y = position.norm_top / this.note.image_height; + } + + x = clamp(x, 0.0, 1.0); + y = clamp(y, 0.0, 1.0); + w = clamp(w, Note.MIN_NOTE_SIZE / this.note.post_width, 1.0); + h = clamp(h, Note.MIN_NOTE_SIZE / this.note.post_height, 1.0); + + this.$note_box.css({ + top: (100 * y) + '%', + left: (100 * x) + '%', + width: (100 * w) + '%', + height: (100 * h) + '%', + }); + } + + copy_style_attributes() { + let $note_box = this.$note_box; + let $attribute_child = $note_box.find('.note-box-attributes'); let has_rotation = false; + Note.COPY_ATTRIBUTES.forEach((attribute)=>{ - const attribute_value = Note.permitted_style_values(attribute, $attribute_child); + const attribute_value = this.permitted_style_values(attribute, $attribute_child); $note_box.css(attribute, attribute_value); + if (attribute === "transform" && attribute_value.startsWith("rotate")) { has_rotation = true; } }); + if (has_rotation) { const current_left = Math.round(parseFloat($note_box.css("left"))); const current_top = Math.round(parseFloat($note_box.css("top"))); - const position = Note.Box.get_min_max_position($note_box); + const position = this.get_min_max_position(); + // Checks for the scenario where the user sets invalid box values through the API // or by adjusting the box dimensions through the browser's dev console before saving if (current_left !== position.norm_left || current_top !== position.norm_top) { @@ -97,135 +152,48 @@ let Note = { top: position.percent_top, left: position.percent_left, }); + $note_box.addClass("out-of-bounds"); } else { $note_box.removeClass("out-of-bounds"); } - $note_box.data('has_rotation', true); + + this.note.has_rotation = true; } else { - $note_box.data('has_rotation', false); + this.note.has_rotation = false; } - }, + } - bind_events: function($note_box) { - $note_box.on( - "dragstart.danbooru resizestart.danbooru", - function(e) { - var $note_box_inner = $(e.currentTarget); - $note_box_inner.addClass("unsaved"); - Note.dragging = true; - Note.clear_timeouts(); - Note.Body.hide_all(); - e.stopPropagation(); - } - ); - - $note_box.on( - "dragstop.danbooru resizestop.danbooru", - function(e) { - Note.dragging = false; - e.stopPropagation(); - } - ); - - $note_box.on( - "mouseover.danbooru mouseout.danbooru", - function(e) { - if (Note.dragging || Utility.test_max_width(660)) { - return; - } - - var $this = $(this); - var $note_box_inner = $(e.currentTarget); - - const note_id = $note_box_inner.data("id"); - if (e.type === "mouseover") { - Note.Body.show(note_id); - if (Note.editing) { - $this.resizable("enable"); - $this.draggable("enable"); - } - $note_box.addClass("hovering"); - } else if (e.type === "mouseout") { - Note.Body.hide(note_id); - if (Note.editing) { - $this.resizable("disable"); - $this.draggable("disable"); - } - $note_box.removeClass("hovering"); - } - - e.stopPropagation(); - } - ); - - $note_box.on( - "click.danbooru", - function (event) { - const note_id = $note_box.data("id"); - if (!Utility.test_max_width(660)) { - $(".note-box").removeClass("movable"); - if (note_id === Note.move_id) { - Note.move_id = null; - } else { - Note.move_id = note_id; - $note_box.addClass("movable"); - } - } else if ($note_box.hasClass("viewing")) { - Note.Body.hide(note_id); - $note_box.removeClass("viewing"); - } else { - $(".note-box").removeClass("viewing"); - Note.Body.show(note_id); - $note_box.addClass("viewing"); - } - } - ); - - $note_box.on("mousedown.danbooru", function(event) { - Note.drag_id = $note_box.data('id'); - }); - - $note_box.on("mouseup.danbooru.drag", Note.Box.drag_stop); - }, - - find: function(id) { - return $(".note-container div.note-box[data-id=" + id + "]"); - }, - - drag_stop: function(event) { - if (Note.drag_id !== null) { - const $image = $("#image"); - const $note_box = Note.Box.find(Note.drag_id); - const dimensions = { - top: (100 * ($note_box.position().top / $image.height())) + '%', - left: (100 * ($note_box.position().left / $image.width())) + '%', - height: (100 * ($note_box.height() / $image.height())) + '%', - width: (100 * ($note_box.width() / $image.width())) + '%', - }; - if (Note.embed && $note_box.data('has_rotation')) { - const position = Note.Box.get_min_max_position($note_box); - Object.assign(dimensions, { - top: position.percentage_top, - left: position.percentage_left, - }); - } - $note_box.css(dimensions); - Note.drag_id = null; + permitted_style_values(attribute, $attribute_child) { + if ($attribute_child.length === 0) { + return ""; } - }, - key_nudge: function(event) { - if (!Note.move_id) { - return; + let found_attribute = $attribute_child.attr('style').split(';').filter(val => val.match(RegExp(`(^| )${attribute}:`))); + if (found_attribute.length === 0) { + return ""; } - const $note_box = Note.Box.find(Note.move_id); - if ($note_box.length === 0) { - return; + + let [, value] = found_attribute[0].trim().split(':').map(val => val.trim()); + if (attribute === "background-color") { + const color_code = $attribute_child.css('background-color'); + return color_code.startsWith('rgba') ? "" : value; } - let computed_style = window.getComputedStyle($note_box[0]); - let current_top = Math.round(parseFloat(computed_style.top)); - let current_left = Math.round(parseFloat(computed_style.left)); + + if (attribute === "transform") { + let rotate_match = value.match(/rotate\([^)]+\)/); + return rotate_match ? rotate_match[0] : ""; + } + + return value; + } + + key_nudge(event) { + let $note_box = this.note.box.$note_box; + + let current_top = Math.round($note_box.position().top); + let current_left = Math.round($note_box.position().left); + switch (event.originalEvent.key) { case "ArrowUp": current_top--; @@ -242,28 +210,25 @@ let Note = { default: // do nothing } - let position = Note.Box.get_min_max_position($note_box, current_top, current_left); + + let position = this.get_min_max_position(current_top, current_left); $note_box.css({ top: position.percent_top, left: position.percent_left, }); + $note_box.addClass("unsaved"); event.preventDefault(); - }, + } - key_resize: function (event) { - if (!Note.move_id) { - return; - } - const $note_box = Note.Box.find(Note.move_id); - if ($note_box.length === 0) { - return; - } - let computed_style = window.getComputedStyle($note_box[0]); - let current_top = Math.round(parseFloat(computed_style.top)); - let current_left = Math.round(parseFloat(computed_style.left)); + key_resize(event) { + let $note_box = this.note.box.$note_box; + + let current_top = Math.round($note_box.position().top); + let current_left = Math.round($note_box.position().left); let current_height = $note_box.height(); let current_width = $note_box.width(); + switch (event.originalEvent.key) { case "ArrowUp": current_height--; @@ -280,27 +245,32 @@ let Note = { default: // do nothing } - const position = Note.Box.get_min_max_position($note_box, null, null, current_height, current_width); + + const position = this.get_min_max_position(null, null, current_height, current_width); if (current_top === position.norm_top && current_left === position.norm_left) { $note_box.css({ height: current_height, width: current_width, }); } + $note_box.addClass("unsaved"); event.preventDefault(); - }, + } - get_min_max_position: function($note_box, current_top = null, current_left = null, current_height = null, current_width = null) { + get_min_max_position(current_top = null, current_left = null, current_height = null, current_width = null) { + let $note_box = this.$note_box; const computed_style = window.getComputedStyle($note_box[0]); + current_top = (current_top === null ? parseFloat(computed_style.top) : current_top); current_left = (current_left === null ? parseFloat(computed_style.left) : current_left); current_height = current_height || $note_box.height(); current_width = current_width || $note_box.width(); - const $image = $("#image"); - const image_height = $image.height(); - const image_width = $image.width(); - const box_data = Note.Box.get_bounding_box($note_box, current_height, current_width); + + const image_height = this.note.image_height; + const image_width = this.note.image_width; + const box_data = this.get_bounding_box(current_height, current_width); + if (((box_data.max_x - box_data.min_x) <= image_width) && ((box_data.max_y - box_data.min_y) <= image_height)) { current_top = Math.min(Math.max(current_top, -box_data.min_y, 0), image_height - box_data.max_y - 2, image_height - box_data.min_y - box_data.max_y - 2, image_height); current_left = Math.min(Math.max(current_left, -box_data.min_x, 0), image_width - box_data.max_x - 2, image_width - box_data.min_x - box_data.max_x - 2, image_width); @@ -308,20 +278,23 @@ let Note = { Utility.error("Box too large to be rotated!"); $note_box.css('transform', 'none'); } + return { norm_top: Math.round(current_top), norm_left: Math.round(current_left), - percent_top: (100 * (current_top / $image.height())) + '%', - percent_left: (100 * (current_left / $image.width())) + '%', + percent_top: (100 * (current_top / image_height)) + '%', + percent_left: (100 * (current_left / image_width)) + '%', }; - }, + } - get_bounding_box: function($note_box, height = null, width = null) { + get_bounding_box(height = null, width = null) { + let $note_box = this.$note_box; height = height || $note_box.height(); width = width || $note_box.width(); let old_coord = [[0, 0], [width, 0], [0, height], [width, height]]; const computed_style = window.getComputedStyle($note_box[0]); const match = computed_style.transform.match(/matrix\(([-e0-9.]+), ([-e0-9.]+)/); + if (!match) { return { min_x: 0, @@ -332,6 +305,7 @@ let Note = { degrees: 0, } } + const costheta = Math.round(match[1] * 1000) / 1000; const sintheta = Math.round(match[2] * 1000) / 1000; let trans_x = width / 2; @@ -340,6 +314,7 @@ let Note = { let max_x = 0; let min_y = Infinity; let max_y = 0; + const new_coord = old_coord.map((coord)=>{ let temp_x = coord[0] - trans_x; let temp_y = coord[1] - trans_y; @@ -353,11 +328,14 @@ let Note = { max_y = Math.max(max_y, new_y); return [new_x, new_y]; }); + const norm_coord = new_coord.map((coord)=>{ return [coord[0] - min_x, coord[1] - min_y]; }); + const radians_per_degree = 0.017453292519943295; const degrees = Math.asin(sintheta) / radians_per_degree; + return { min_x: min_x, min_y: min_y, @@ -366,72 +344,62 @@ let Note = { norm_coord: norm_coord, degrees: degrees, }; - }, + } - show_highlighted: function($note_box) { - var note_id = $note_box.data("id"); - - Note.Body.show(note_id); + show_highlighted() { + this.note.body.show(); $(".note-box-highlighted").removeClass("note-box-highlighted"); - $note_box.addClass("note-box-highlighted"); - $note_box[0].scrollIntoView(false); - }, + this.$note_box.addClass("note-box-highlighted"); + this.$note_box[0].scrollIntoView(false); + } - scale: function($note_box) { - var $image = $("#image"); - var original_width = $image.data("original-width"); - var original_height = $image.data("original-height"); - var x_percent = 100 * ($note_box.data('x') / original_width); - var y_percent = 100 * ($note_box.data('y') / original_height); - var height_percent = 100 * ($note_box.data('height') / original_height); - var width_percent = 100 * ($note_box.data('width') / original_width); - $note_box.css({ - top: y_percent + '%', - left: x_percent + '%', - width: width_percent + '%', - height: height_percent + '%', - }); - }, + // Rescale font sizes of embedded notes when the image is resized. + static scale_all() { + let $image = $("#image"); + let $container = $(".note-container"); - scale_all: function() { - const $container = $('.note-container'); if ($container.length === 0) { return; } - let $image = $("#image"); - if (Note.embed) { - let large_width = parseFloat($image.data('large-width')); - let ratio = $image.width() / large_width; - let font_percentage = ratio * 100; - $container.css('font-size', font_percentage + '%'); - } - }, - toggle_all: function() { + let large_width = parseFloat($image.data('large-width')); + let ratio = $image.width() / large_width; + let font_percentage = ratio * 100; + + $container.css('font-size', font_percentage + '%'); + } + + static toggle_all() { $(".note-container").toggleClass("hide-notes"); } - }, + } - Body: { - create: function(id) { - var $note_body = $('
'); - $note_body.addClass("note-body"); - $note_body.data("id", String(id)); - $note_body.attr("data-id", String(id)); - $note_body.hide(); - Note.Body.bind_events($note_body); - return $note_body; - }, + static Body = class { + note = null; + $note_body = null; - initialize: function($note_body) { - var $note_box = Note.Box.find($note_body.data("id")); - if (Note.embed && $note_box.data('has_rotation')) { - const box_data = Note.Box.get_bounding_box($note_box); + constructor(note) { + this.note = note; + this.$note_body = $('
'); + this.note.$note_container.append(this.$note_body); + + this.$note_body.on("mouseover.danbooru", this.on_mouseover.bind(this)); + this.$note_body.on("mouseout.danbooru", this.on_mouseout.bind(this)); + this.$note_body.on("click.danbooru", this.on_click.bind(this)); + } + + initialize() { + let $note_body = this.$note_body; + let $note_box = this.note.box.$note_box; + + if (this.note.embed && this.note.has_rotation) { + const box_data = this.note.box.get_bounding_box(); // Select the lowest box corner to the farthest left let selected_corner = box_data.norm_coord.reduce(function (selected, coord) {return (selected[1] > coord[1]) || (selected[1] === coord[1] && selected[0] < coord[0]) ? selected : coord;}); let normalized_degrees = box_data.degrees % 90.0; // Align to the left or right body corner depending upon the box angle let body_corner = $note_box.position().left - (normalized_degrees > 0.0 && normalized_degrees <= 45.0 ? $note_body.width() : 0); + $note_body.css({ top: $note_box.position().top + selected_corner[1] + 5, left: body_corner + selected_corner[0], @@ -442,46 +410,47 @@ let Note = { left: $note_box.position().left, }); } - Note.Body.bound_position($note_body); - }, - bound_position: function($note_body) { - var $image = $("#image"); + this.bound_position(); + } + + bound_position() { + var $image = this.note.$note_container; var doc_width = $image.offset().left + $image.width(); + let $note_body = this.$note_body; if ($note_body.offset().left + $note_body.width() > doc_width) { $note_body.css({ left: $note_body.position().left - 10 - ($note_body.offset().left + $note_body.width() - doc_width) }); } - }, + } - show: function(id) { + show() { Note.Body.hide_all(); - Note.clear_timeouts(); - var $note_body = Note.Body.find(id); - if (!$note_body.data('resized')) { - Note.Body.resize($note_body); - $note_body.data('resized', 'true'); + + if (!this.resized) { + this.resize(); + this.resized = true; } - $note_body.show(); - Note.Body.initialize($note_body); - }, - find: function(id) { - return $(".note-container div.note-body[data-id=" + id + "]"); - }, + this.$note_body.show(); + this.initialize(); + } - hide: function(id) { - var $note_body = Note.Body.find(id); - Note.timeouts.push(setTimeout(() => $note_body.hide(), Note.HIDE_DELAY)); - }, + hide(delay = Note.HIDE_DELAY) { + Note.timeouts.push(setTimeout(() => this.$note_body.hide(), delay)); + } - hide_all: function() { + static hide_all() { + Note.timeouts.forEach(clearTimeout); + Note.timeouts = []; $(".note-container div.note-body").hide(); - }, + } + + resize() { + let $note_body = this.$note_body; - resize: function($note_body) { $note_body.css("min-width", ""); var w = $note_body.width(); var h = $note_body.height(); @@ -525,75 +494,79 @@ let Note = { $note_body.css("min-width", hi); } } - }, + } + + display_text(text) { + this.set_text(text); + + if (this.note.embed) { + let $note_inner_box = this.note.box.$inner_border; - set_text: function($note_body, $note_box, text) { - if (Note.embed) { - const $note_inner_box = $note_box.find("div.note-box-inner-border"); - Note.Body.display_text($note_inner_box, text); // Reset the font size so that the normalization calculations will be correct - $note_inner_box.css("font-size", Note.base_font_size + "px"); - Note.normalize_sizes($note_inner_box.children(), Note.base_font_size); + $note_inner_box.css("font-size", this.note.base_font_size + "px"); + this.note.normalize_sizes($note_inner_box.children(), this.note.base_font_size); + // Clear the font size so that the fonts will be scaled to the current value $note_inner_box.css("font-size", ""); - Note.Box.copy_style_attributes($note_box); - } else { - Note.Body.display_text($note_body, text); + this.note.box.copy_style_attributes(); } - Note.Body.resize($note_body); - Note.Body.bound_position($note_body); - }, - display_text: function($note_body, text) { + this.resize(); + this.bound_position(); + } + + set_text(text) { + text = text ?? ""; text = text.replace(//g, '

'); text = text.replace(/<\/tn>/g, '

'); text = text.replace(/\n/g, '
'); - $note_body.html(text); - }, - bind_events: function($note_body) { - $note_body.on("mouseover.danbooru", function(e) { - var $note_body_inner = $(e.currentTarget); - Note.Body.show($note_body_inner.data("id")); - e.stopPropagation(); - }); - - $note_body.on("mouseout.danbooru", function(e) { - var $note_body_inner = $(e.currentTarget); - Note.Body.hide($note_body_inner.data("id")); - e.stopPropagation(); - }); - - if (CurrentUser.data("is-anonymous") === false) { - $note_body.on("click.danbooru", function(e) { - if (e.target.tagName !== "A") { - var $note_body_inner = $(e.currentTarget); - Note.Edit.show($note_body_inner); - } - e.stopPropagation(); - }); + if (this.note.embed) { + this.note.box.$inner_border.html(text); + this.$note_body.html("Click to edit"); + } else if (text) { + this.$note_body.html(text); } else { - $note_body.on("click.danbooru", function(e) { - if (e.target.tagName !== "A") { - Utility.notice("You must be logged in to edit notes"); - } - e.stopPropagation(); - }); + this.$note_body.html("Click to edit"); } } - }, - Edit: { - show: function($note_body) { - var id = $note_body.data("id"); + async preview_text(text) { + this.display_text("Loading..."); + let response = await $.getJSON("/note_previews", { body: text }); - if (Note.editing) { + this.display_text(response.body); + this.initialize(); + this.$note_body.show(); + } + + on_mouseover(e) { + this.show(); + } + + on_mouseout() { + this.hide(); + } + + on_click(e) { + // don't open the note edit dialog when the user clicks a link in the note body. + if ($(e.target).is("a")) { return; } - $(".note-box").resizable("disable"); - $(".note-box").draggable("disable"); - $(".note-box").addClass("editing"); + if (CurrentUser.data("is-anonymous")) { + Utility.notice("You must be logged in to edit notes"); + } else { + Note.Edit.show(this.note); + } + } + } + + static Edit = class { + static show(note) { + if ($(".note-box").hasClass("editing")) { + return; + } let $textarea = $(''); $textarea.css({ @@ -602,15 +575,16 @@ let Note = { resize: "none", }); - if (!$note_body.hasClass("new-note")) { - $textarea.val($note_body.data("original-body")); + if (!note.is_new()) { + $textarea.val(note.original_body); } let $dialog = $('
'); - let note_title = (typeof id === 'string' && id.startsWith('x') ? 'Creating new note' : 'Editing note #' + id); + let note_title = note.is_new() ? 'Creating new note' : `Editing note #${note.id}`; + $dialog.append('' + note_title + ' (view help)'); $dialog.append($textarea); - $dialog.data("id", id); + $dialog.dialog({ width: 360, height: 240, @@ -622,448 +596,321 @@ let Note = { classes: { "ui-dialog": "note-edit-dialog", }, + open: () => { + $(".note-box").addClass("editing"); + }, + close: () => { + $(".note-box").removeClass("editing"); + }, buttons: { - "Save": Note.Edit.save, - "Preview": Note.Edit.preview, - "Cancel": Note.Edit.cancel, - "Delete": Note.Edit.destroy, - "History": Note.Edit.history + "Save": () => Note.Edit.save($dialog, note), + "Preview": () => Note.Edit.preview($dialog, note), + "Cancel": () => Note.Edit.cancel($dialog, note), + "Delete": () => Note.Edit.destroy($dialog, note), + "History": () => Note.Edit.history($dialog, note), } }); - $dialog.data("uiDialog")._title = function(title) { - title.html(this.options.title); // Allow unescaped html in dialog title - } - - $dialog.on("dialogclose.danbooru", function() { - Note.editing = false; - $(".note-box").resizable("enable"); - $(".note-box").draggable("enable"); - $(".note-box").removeClass("editing"); - }); $textarea.selectEnd(); - Note.editing = true; - }, - - parameterize_note: function($note_box, $note_body) { - var $image = $("#image"); - var original_width = parseInt($image.data("original-width")); - var ratio = parseInt($image.width()) / original_width; - - var hash = { - note: { - x: Math.round($note_box.position().left / ratio), - y: Math.round($note_box.position().top / ratio), - width: Math.round($note_box.width() / ratio), - height: Math.round($note_box.height() / ratio), - body: $note_body.data("original-body"), - } - } - - if ($note_box.data("id").match(/x/)) { - hash.note.html_id = $note_box.data("id"); - hash.note.post_id = Utility.meta("post-id"); - } - - return hash; - }, - - error_handler: function(xhr, status, exception) { - Utility.error("Error: " + (xhr.responseJSON.reason || xhr.responseJSON.reasons.join("; "))); - }, - - success_handler: function(data, status, xhr) { - var $note_box = null; - - if (data.html_id) { // new note - var $note_body = Note.Body.find(data.html_id); - $note_box = Note.Box.find(data.html_id); - $note_body.data("id", String(data.id)).attr("data-id", data.id); - $note_box.data("id", String(data.id)).attr("data-id", data.id); - $note_box.removeClass("new-note"); - $note_box.removeClass("unsaved"); - $note_box.removeClass("movable"); - } else { - $note_box = Note.Box.find(data.id); - $note_box.removeClass("unsaved"); - $note_box.removeClass("movable"); - } - Note.move_id = null; - }, - - save: function() { - var $this = $(this); - var $textarea = $this.find("textarea"); - var id = $this.data("id"); - var $note_body = Note.Body.find(id); - var $note_box = Note.Box.find(id); - var text = $textarea.val(); - $note_body.data("original-body", text); - Note.Body.set_text($note_body, $note_box, "Loading..."); - $.get("/note_previews.json", {body: text}).then(function(data) { - Note.Body.set_text($note_body, $note_box, data.body); - Note.Body.initialize($note_body); - $note_body.show(); - }); - $this.dialog("close"); - - if (id.match(/\d/)) { - $.ajax("/notes/" + id + ".json", { - type: "PUT", - data: Note.Edit.parameterize_note($note_box, $note_body), - error: Note.Edit.error_handler, - success: Note.Edit.success_handler - }); - } else { - $.ajax("/notes.json", { - type: "POST", - data: Note.Edit.parameterize_note($note_box, $note_body), - error: Note.Edit.error_handler, - success: Note.Edit.success_handler - }); - } - }, - - preview: function() { - var $this = $(this); - var $textarea = $this.find("textarea"); - var id = $this.data("id"); - var $note_body = Note.Body.find(id); - var text = $textarea.val(); - var $note_box = Note.Box.find(id); - $note_box.addClass("unsaved"); - Note.Body.set_text($note_body, $note_box, "Loading..."); - $.get("/note_previews.json", {body: text}).then(function(data) { - Note.Body.set_text($note_body, $note_box, data.body); - Note.Body.initialize($note_body); - $note_body.show(); - }); - }, - - cancel: function() { - $(this).dialog("close"); - }, - - destroy: function() { - if (!confirm("Do you really want to delete this note?")) { - return - } - - var $this = $(this); - var id = $this.data("id"); - - if (id.match(/\d/)) { - $.ajax("/notes/" + id + ".json", { - type: "DELETE", - success: function() { - Note.Box.find(id).remove(); - Note.Body.find(id).remove(); - $this.dialog("close"); - } - }); - } - }, - - history: function() { - var $this = $(this); - var id = $this.data("id"); - if (id.match(/\d/)) { - window.location.href = "/note_versions?search[note_id]=" + id; - } - $(this).dialog("close"); } - }, - TranslationMode: { - active: false, + static async save($dialog, note) { + let $note_box = note.box.$note_box; + let text = $dialog.find("textarea").val(); - toggle: function(e) { - if (Note.TranslationMode.active) { - Note.TranslationMode.stop(e); + let params = { + x: Math.round(note.x * note.post_width), + y: Math.round(note.y * note.post_height), + width: Math.round(note.width * note.post_width), + height: Math.round(note.height * note.post_height), + body: text + }; + + note.original_body = text; + note.body.preview_text(text); + + try { + if (note.is_new()) { + params.post_id = note.post_id; + let response = await $.ajax("/notes.json", { type: "POST", data: { note: params }}); + note.id = response.id; + } else { + await $.ajax(`/notes/${note.id}.json`, { type: "PUT", data: { note: params }}); + } + + $dialog.dialog("close"); + $note_box.removeClass("unsaved"); + } catch (xhr) { + Utility.error("Error: " + (xhr.responseJSON.reason || xhr.responseJSON.reasons.join("; "))); + } + } + + static async preview($dialog, note) { + let text = $dialog.find("textarea").val(); + + note.box.$note_box.addClass("unsaved"); + note.body.preview_text(text); + } + + static cancel($dialog, _note) { + $dialog.dialog("close"); + } + + static async destroy($dialog, note) { + if (!confirm("Do you really want to delete this note?")) { + return; + } + + if (!note.is_new()) { + await $.ajax(`/notes/${note.id}.json`, { type: "DELETE" }); + note.box.$note_box.remove(); + note.body.$note_body.remove(); + $dialog.dialog("close"); + } + } + + static history($dialog, note) { + if (!note.is_new()) { + window.location.href = `/note_versions?search[note_id]=${note.id}`; + } + + $dialog.dialog("close"); + } + } + + static TranslationMode = class { + static toggle() { + if ($("body").hasClass("mode-translation")) { + Note.TranslationMode.stop(); } else { - Note.TranslationMode.start(e); + Note.TranslationMode.start(); } - }, + } - start: function(e) { - e.preventDefault(); - - if (CurrentUser.data("is-anonymous")) { - Utility.notice("You must be logged in to edit notes"); - return; - } - - if (Note.TranslationMode.active) { - return; - } - - $("#image").css("cursor", "crosshair"); - Note.TranslationMode.active = true; + static start() { $(document.body).addClass("mode-translation"); $("#image").off("click.danbooru", Note.Box.toggle_all); $("#image").on("mousedown.danbooru.note", Note.TranslationMode.Drag.start); - $(document).on("mouseup.danbooru.note", Note.TranslationMode.Drag.stop); - $("#mark-as-translated-section").show(); Utility.notice('Translation mode is on. Drag on the image to create notes. Turn translation mode off (shortcut is n).'); $("#notice a:contains(Turn translation mode off)").on("click.danbooru", Note.TranslationMode.stop); - }, + } - stop: function(e) { - e.preventDefault(); - - Note.TranslationMode.active = false; - $("#image").css("cursor", "auto"); + static stop() { + $("#note-preview").hide(); $("#image").on("click.danbooru", Note.Box.toggle_all); $("#image").off("mousedown.danbooru.note", Note.TranslationMode.Drag.start); - $(document).off("mouseup.danbooru.note", Note.TranslationMode.Drag.stop); + $(document).off("mouseup.danbooru", Note.TranslationMode.Drag.stop); + $(document).off("mousemove.danbooru", Note.TranslationMode.Drag.drag); $(document.body).removeClass("mode-translation"); $("#close-notice-link").click(); - $("#mark-as-translated-section").hide(); - }, + } - create_note: function(e, x, y, w, h) { - if (w > 9 || h > 9) { /* minimum note size: 10px */ - if (w <= 9) { - w = 10; - } else if (h <= 9) { - h = 10; - } - Note.create(x, y, w, h); - } + static Drag = class { + static dragStartX = 0; + static dragStartY = 0; - $(".note-container").removeClass("hide-notes"); - e.stopPropagation(); - e.preventDefault(); - }, - - Drag: { - dragging: false, - dragStartX: 0, - dragStartY: 0, - dragDistanceX: 0, - dragDistanceY: 0, - x: 0, - y: 0, - w: 0, - h: 0, - - start: function (e) { + static start(e) { if (e.which !== 1) { return; } + e.preventDefault(); /* don't drag the image */ $(document).on("mousemove.danbooru", Note.TranslationMode.Drag.drag); + $(document).on("mouseup.danbooru", Note.TranslationMode.Drag.stop); Note.TranslationMode.Drag.dragStartX = e.pageX; Note.TranslationMode.Drag.dragStartY = e.pageY; - }, + } - drag: function (e) { - Note.TranslationMode.Drag.dragDistanceX = e.pageX - Note.TranslationMode.Drag.dragStartX; - Note.TranslationMode.Drag.dragDistanceY = e.pageY - Note.TranslationMode.Drag.dragStartY; + static drag(e) { var $image = $("#image"); var offset = $image.offset(); - var limitX1 = $image.width() - Note.TranslationMode.Drag.dragStartX + offset.left - 1; - var limitX2 = offset.left - Note.TranslationMode.Drag.dragStartX; - var limitY1 = $image.height() - Note.TranslationMode.Drag.dragStartY + offset.top - 1; - var limitY2 = offset.top - Note.TranslationMode.Drag.dragStartY; - if (Note.TranslationMode.Drag.dragDistanceX > limitX1) { - Note.TranslationMode.Drag.dragDistanceX = limitX1; - } else if (Note.TranslationMode.Drag.dragDistanceX < limitX2) { - Note.TranslationMode.Drag.dragDistanceX = limitX2; + // (x0, y0) is the top left point of the drag box. (x1, y1) is the bottom right point. + let x0 = clamp(e.pageX, offset.left, Note.TranslationMode.Drag.dragStartX); + let y0 = clamp(e.pageY, offset.top, Note.TranslationMode.Drag.dragStartY); + let x1 = clamp(e.pageX, Note.TranslationMode.Drag.dragStartX, offset.left + $image.width()); + let y1 = clamp(e.pageY, Note.TranslationMode.Drag.dragStartY, offset.top + $image.height()); + + // Convert from page-relative coordinates to image-relatives coordinates. + let x = x0 - offset.left; + let y = y0 - offset.top; + let w = x1 - x0; + let h = y1 - y0; + + // Only show the new note box after we've dragged a minimum distance. This is to avoid + // accidentally creating tiny notes if we drag a small distance while trying to toggle notes. + if (w >= Note.MIN_NOTE_SIZE || h >= Note.MIN_NOTE_SIZE) { + $("#note-preview").show(); } - if (Note.TranslationMode.Drag.dragDistanceY > limitY1) { - Note.TranslationMode.Drag.dragDistanceY = limitY1; - } else if (Note.TranslationMode.Drag.dragDistanceY < limitY2) { - Note.TranslationMode.Drag.dragDistanceY = limitY2; + if ($("#note-preview").is(":visible")) { + $('#note-preview').css({ left: x, top: y, width: w, height: h }); } + } - if (Math.abs(Note.TranslationMode.Drag.dragDistanceX) > 9 && Math.abs(Note.TranslationMode.Drag.dragDistanceY) > 9) { - Note.TranslationMode.Drag.dragging = true; /* must drag at least 10pixels (minimum note size) in both dimensions. */ - } - if (Note.TranslationMode.Drag.dragging) { - if (Note.TranslationMode.Drag.dragDistanceX >= 0) { - Note.TranslationMode.Drag.x = Note.TranslationMode.Drag.dragStartX - offset.left; - Note.TranslationMode.Drag.w = Note.TranslationMode.Drag.dragDistanceX; - } else { - Note.TranslationMode.Drag.x = Note.TranslationMode.Drag.dragStartX - offset.left + Note.TranslationMode.Drag.dragDistanceX; - Note.TranslationMode.Drag.w = -Note.TranslationMode.Drag.dragDistanceX; - } - - if (Note.TranslationMode.Drag.dragDistanceY >= 0) { - Note.TranslationMode.Drag.y = Note.TranslationMode.Drag.dragStartY - offset.top; - Note.TranslationMode.Drag.h = Note.TranslationMode.Drag.dragDistanceY; - } else { - Note.TranslationMode.Drag.y = Note.TranslationMode.Drag.dragStartY - offset.top + Note.TranslationMode.Drag.dragDistanceY; - Note.TranslationMode.Drag.h = -Note.TranslationMode.Drag.dragDistanceY; - } - - $('#note-preview').css({ - display: 'block', - left: (Note.TranslationMode.Drag.x + 1), - top: (Note.TranslationMode.Drag.y + 1), - width: (Note.TranslationMode.Drag.w - 3), - height: (Note.TranslationMode.Drag.h - 3) - }); - } - }, - - stop: function (e) { - if (e.which !== 1) { - return; - } - if (Note.TranslationMode.Drag.dragStartX === 0) { - return; /* 'stop' is bound to window, don't create note if start wasn't triggered */ - } + static stop() { $(document).off("mousemove.danbooru", Note.TranslationMode.Drag.drag); + $(document).off("mouseup.danbooru", Note.TranslationMode.Drag.stop); - if (Note.TranslationMode.Drag.dragging) { - $('#note-preview').css({ display: 'none' }); - Note.TranslationMode.create_note(e, Note.TranslationMode.Drag.x, Note.TranslationMode.Drag.y, Note.TranslationMode.Drag.w - 1, Note.TranslationMode.Drag.h - 1); - Note.TranslationMode.Drag.dragging = false; /* border of the note is pixel-perfect on the preview border */ - } else { /* no dragging -> toggle display of notes */ + if ($("#note-preview").is(":visible")) { + let $image = $("#image"); + + new Note({ + x: $("#note-preview").position().left / $image.width(), + y: $("#note-preview").position().top / $image.height(), + w: $("#note-preview").width() / $image.width(), + h: $("#note-preview").height() / $image.height(), + }); + + $("#note-preview").hide(); + $(".note-container").removeClass("hide-notes"); + } else { /* If we didn't drag far enough, treat it as a click and toggle displaying notes. */ Note.Box.toggle_all(); } - - Note.TranslationMode.Drag.dragStartX = 0; - Note.TranslationMode.Drag.dragStartY = 0; } } - }, + } - id: "x", - dragging: false, - editing: false, - move_id: null, - drag_id: null, - base_font_size: null, - timeouts: [], - pending: {}, + constructor({ x, y, w, h, id = null, original_body = null, sanitized_body = null } = {}) { + this.$note_container = $(".note-container"); - add: function(container, id, x, y, w, h, original_body, sanitized_body) { - var $note_box = Note.Box.create(id); - var $note_body = Note.Body.create(id); + this.id = id; + this.post_id = this.$note_container.data("id"); + this.embed = Utility.meta("post-has-embedded-notes") === "true"; + this.original_body = original_body; - $note_box.data('x', x); - $note_box.data('y', y); - $note_box.data('width', w); - $note_box.data('height', h); - container.appendChild($note_box[0]); - container.appendChild($note_body[0]); - $note_body.data("original-body", original_body); - Note.Box.scale($note_box); - if (Note.embed) { - Note.Body.display_text($note_box.children("div.note-box-inner-border"), sanitized_body); - Note.Body.display_text($note_body, "Click to edit"); - } else { - Note.Body.display_text($note_body, sanitized_body); - } - }, + this.box = new Note.Box(this); + this.body = new Note.Body(this); - create: function(x, y, w, h) { - var $note_box = Note.Box.create(Note.id); - var $note_body = Note.Body.create(Note.id); - $note_box.css({ - top: y, - left: x, - width: w, - height: h - }); - Note.Box.update_data_attributes($note_box); - Note.Box.scale($note_box); - $note_box.addClass("new-note"); - $note_box.addClass("unsaved"); - $note_body.html("Click to edit"); - $(".note-container").append($note_box); - $(".note-container").append($note_body); - Note.id += "x"; - }, + this.box.place_note(x, y, w, h); + this.body.display_text(sanitized_body); - normalize_sizes: function ($note_elements, parent_font_size) { + Note.notes.push(this); + } + + is_new() { + return this.id === null; + } + + // The coordinates of the top left corner of the note box, as floats in the + // range 0.0 to 1.0. Does not account for note rotation. + get x() { + return this.box.$note_box.position().left / this.image_width; + } + + get y() { + return this.box.$note_box.position().top / this.image_height; + } + + // The current width and height of the note box, as floats in the range 0.0 to 1.0. + get width() { + return this.box.$note_box.width() / this.image_width; + } + + get height() { + return this.box.$note_box.height() / this.image_height; + } + + // The width and height of the full-size original image in pixels. + get post_width() { + return parseInt(this.$note_container.attr("data-width")); + } + + get post_height() { + return parseInt(this.$note_container.attr("data-height")); + } + + // The current width and height of the image in pixels. Will be smaller than the post width + // if the sample image is being displayed, or if the image is resized to fit the screen. + get image_width() { + return parseInt(this.$note_container.width()); + } + + get image_height() { + return parseInt(this.$note_container.height()); + } + + // The initial font size of the note container. Embedded notes are scaled relative to this value. + get base_font_size() { + return parseFloat(this.$note_container.parent().css("font-size")); + } + + normalize_sizes($note_elements, parent_font_size) { if ($note_elements.length === 0) { return; } - $note_elements.each(function(i, element) { + + $note_elements.toArray().forEach((element) => { const $element = $(element); const computed_styles = window.getComputedStyle(element); const font_size = parseFloat(computed_styles.fontSize); - Note.NORMALIZE_ATTRIBUTES.forEach(function(attribute) { + + Note.NORMALIZE_ATTRIBUTES.forEach((attribute) => { const original_size = parseFloat(computed_styles[attribute]) || 0; const relative_em = original_size / font_size; $element.css(attribute, relative_em + "em"); }); + const font_percentage = 100 * (font_size / parent_font_size); $element.css("font-size", font_percentage + "%"); $element.attr("size", ""); - Note.normalize_sizes($element.children(), font_size); + + this.normalize_sizes($element.children(), font_size); }); - }, + } - clear_timeouts: function() { - Note.timeouts.forEach(clearTimeout); - Note.timeouts = []; - }, + static find(id) { + return Note.notes.find(note => note.id === id); + } - load_all: function() { - var fragment = document.createDocumentFragment(); - $.each($("#notes article"), function(i, article) { + static load_all() { + let $image = $(".image-container"); + + $("#notes article").toArray().forEach(article => { var $article = $(article); - Note.add( - fragment, - $article.data("id"), - $article.data("x"), - $article.data("y"), - $article.data("width"), - $article.data("height"), - $article.data("body"), - $article.html() - ); - }); - const $note_container = $(".note-container"); - $note_container.append(fragment); - if (Note.embed) { - Note.base_font_size = parseFloat(window.getComputedStyle($note_container[0]).fontSize); - $.each($(".note-box"), function(i, note_box) { - const $note_box = $(note_box); - Note.normalize_sizes($("div.note-box-inner-border", note_box).children(), Note.base_font_size); - // Accounting for transformation values calculations which aren't correct immediately on page load - setTimeout(()=>{Note.Box.copy_style_attributes($note_box);}, 100); - }); - } - Note.Box.scale_all(); - }, - initialize_all: function() { + new Note({ + id: $article.data("id"), + x: $article.data("x") / $image.data("width"), + y: $article.data("y") / $image.data("height"), + w: $article.data("width") / $image.data("width"), + h: $article.data("height") / $image.data("height"), + original_body: $article.data("body"), + sanitized_body: $article.html() + }); + }); + } + + static initialize_all() { if ($("#c-posts #a-show #image").length === 0 || $("video#image").length) { return; } - Note.embed = (Utility.meta("post-has-embedded-notes") === "true"); Note.load_all(); + Note.Box.scale_all(); + + $(document).on("click.danbooru", "#translate", (e) => { + Note.TranslationMode.toggle(); + e.preventDefault(); + }); - this.initialize_shortcuts(); this.initialize_highlight(); $(document).on("hashchange.danbooru.note", this.initialize_highlight); - Utility.keydown("up down left right", "nudge_note", Note.Box.key_nudge); - Utility.keydown("shift+up shift+down shift+left shift+right", "resize_note", Note.Box.key_resize); + $(window).on("resize.danbooru.note_scale", Note.Box.scale_all); - }, - - initialize_shortcuts: function() { - $("#translate").on("click.danbooru", Note.TranslationMode.toggle); $("#image").on("click.danbooru", Note.Box.toggle_all); - }, + } - initialize_highlight: function() { + static initialize_highlight() { var matches = window.location.hash.match(/^#note-(\d+)$/); if (matches) { - var $note_box = Note.Box.find(matches[1]); - Note.Box.show_highlighted($note_box); + let note_id = parseInt(matches[1]); + let note = Note.find(note_id); + note.box.show_highlighted(); } - }, + } } $(function() { diff --git a/app/javascript/src/javascripts/utility.js b/app/javascript/src/javascripts/utility.js index 4a335fd50..699278d7b 100644 --- a/app/javascript/src/javascripts/utility.js +++ b/app/javascript/src/javascripts/utility.js @@ -3,6 +3,10 @@ import Rails from '@rails/ujs'; let Utility = {}; +export function clamp(value, low, high) { + return Math.max(low, Math.min(value, high)); +} + Utility.delay = function(milliseconds) { return new Promise(resolve => setTimeout(resolve, milliseconds)); } diff --git a/app/javascript/src/styles/specific/notes.scss b/app/javascript/src/styles/specific/notes.scss index ba02223ad..6fd6c9a59 100644 --- a/app/javascript/src/styles/specific/notes.scss +++ b/app/javascript/src/styles/specific/notes.scss @@ -7,7 +7,9 @@ } div.note-body { + display: none; position: absolute; + font-size: 14px; border: var(--note-body-border); background: var(--note-body-background); color: var(--note-body-text-color); @@ -81,7 +83,7 @@ justify-content: center; align-items: center; text-align: center; - position: absolute; + position: absolute !important; border: var(--note-box-border); min-width: 5px; min-height: 5px; @@ -114,10 +116,6 @@ &.movable { opacity: 1; } - - div.ui-resizable-handle { - display: block; - } } &.editing, @@ -134,19 +132,25 @@ border: var(--movable-note-box-border); } - div.ui-resizable-handle { - display: none; + &:not(.hovering) div.ui-resizable-handle { + display: none !important; } } &.note-box-highlighted { outline: 2px solid var(--note-highlight-color); } + + div.ui-resizable-handle { + position: absolute; + } } } +/* the box that appears when dragging to create a new note. */ div#note-preview { position: absolute; + cursor: crosshair; border: var(--note-preview-border); opacity: 0.6; display: none; diff --git a/app/javascript/src/styles/specific/posts.scss b/app/javascript/src/styles/specific/posts.scss index f6dfee59f..c657cc29d 100644 --- a/app/javascript/src/styles/specific/posts.scss +++ b/app/javascript/src/styles/specific/posts.scss @@ -436,6 +436,14 @@ body[data-post-current-image-size="original"] #image-resize-notice { display: none; } +body.mode-translation .note-container { + cursor: crosshair; +} + +body:not(.mode-translation) div#c-posts div#a-show #mark-as-translated-section { + display: none; +} + div#c-post-versions, div#c-artist-versions { div#a-index { a { diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index 29d8467a9..b035b389c 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -61,7 +61,7 @@ <% end %> <% end %> -