From: Vincent Vanwaelscappel Date: Thu, 23 Jan 2025 11:44:02 +0000 (+0100) Subject: wip #7273 @1.5 X-Git-Url: http://git.cubedesigners.com/?a=commitdiff_plain;h=b9805094bcdfbaa97a5a70818e77e6b0cb327eb8;p=fluidbook-toolbox.git wip #7273 @1.5 --- diff --git a/app/Http/Controllers/Admin/Operations/FluidbookPublication/LinksOperation.php b/app/Http/Controllers/Admin/Operations/FluidbookPublication/LinksOperation.php index 6f53d8776..fa114b283 100644 --- a/app/Http/Controllers/Admin/Operations/FluidbookPublication/LinksOperation.php +++ b/app/Http/Controllers/Admin/Operations/FluidbookPublication/LinksOperation.php @@ -4,23 +4,17 @@ namespace App\Http\Controllers\Admin\Operations\FluidbookPublication; // __('!!Paramètres des fluidbooks') -use App\Fluidbook\Compiler\Compiler; use App\Fluidbook\Link\LinksData; use App\Models\FluidbookPublication; use App\Models\User; use Cubist\Backpack\Http\Controllers\Base\XSendFileController; use Cubist\Util\Files\Files; -use Fluidbook\Tools\Links\Link; -use Fluidbook\Tools\Links\TextLink; +use Fluidbook\Tools\FluidbookFontToWoff; use Illuminate\Http\UploadedFile; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Route; use PhpOffice\PhpSpreadsheet\Writer\Xlsx; -use Illuminate\Support\Facades\Broadcast; use Illuminate\Support\Str; use Illuminate\Http\Request; -use Prologue\Alerts\Facades\Alert; -use Fluidbook\Tools\FluidbookFontToWoff; trait LinksOperation @@ -31,6 +25,7 @@ trait LinksOperation protected function setupLinksRoutes($segment, $routeName, $controller) { + Route::match(['get'], $segment . '/{id}/edit/links-beta', $controller . '@linksBeta')->name('fluidbook_linkeditor_beta'); Route::match(['get'], $segment . '/{id}/edit/links', $controller . '@links')->name('fluidbook_linkeditor'); Route::match(['post'], $segment . '/{id}/edit/links', $controller . '@broadcast')->name('fluidbook_linkeditor_post'); Route::match(['get'], $segment . '/{id}/edit/links/versions', $controller . '@getLinkVersions'); @@ -85,27 +80,28 @@ trait LinksOperation return response()->json(['assets' => $fb->getLinksAssetsDimensions(), 'versions' => LinksData::getLinksVersions($fluidbook_id)]); } - protected function generateFont(Request $request, $id) { + protected function generateFont(Request $request, $id) + { $l = $request->links; - $textLinks = array_filter($l, function($n) { + $textLinks = array_filter($l, function ($n) { return $n['type'] === '35'; }); $fb = FluidbookPublication::find($id); - $wdir = $fb->protected_path('fluidbookpublication/working/'.$id).'/'; + $wdir = $fb->protected_path('fluidbookpublication/working/' . $id) . '/'; $css = []; foreach ($textLinks as $link) { - if(array_key_exists('image', $link)) { + if (array_key_exists('image', $link)) { $fontFile = $link['image']; $hash = 'fb_' . substr(md5($fontFile), 0, 10); $final = $hash . '.woff'; $filepath = $wdir . '/' . $link['image']; $dest = $wdir . '/' . $final; - if(!file_exists($dest)) { + if (!file_exists($dest)) { FluidbookFontToWoff::fontforge($dest, $filepath); } - $config = FluidbookFontToWoff::configFont($fontFile,$hash,$filepath); + $config = FluidbookFontToWoff::configFont($fontFile, $hash, $filepath); $css[$hash]['capHeight'] = $config['capHeight']; $css[$hash]['font'] = $hash; $css[$hash]['hash'] = $final; @@ -330,7 +326,18 @@ trait LinksOperation $token = Str::random(10); - return view('fluidbook_publication.link_editor', ['id' => $id, 'fluidbook' => FluidbookPublication::find($id), 'access' => "", 'token' => $token]); + return view('fluidbook_publication.link_editor', ['version' => 'stable', 'id' => $id, 'fluidbook' => FluidbookPublication::find($id), 'access' => "", 'token' => $token]); + } + + public function linksBeta($id, Request $request) + { + if (!FluidbookPublication::hasPermission($id)) { + abort(401); + } + + $token = Str::random(10); + + return view('fluidbook_publication.link_editor', ['version' => 'beta', 'id' => $id, 'fluidbook' => FluidbookPublication::find($id), 'access' => "", 'token' => $token]); } public function broadcast(Request $request) @@ -345,16 +352,16 @@ trait LinksOperation $queueEditor = cache()->get('queue_editor' . $fluidbookId); // on récupère la liste des connexions // on extrait seulement les connexions qui datent de pas moins de 5secondes - if(!empty($queueEditor)) { - if(sizeof($queueEditor) > 0) { + if (!empty($queueEditor)) { + if (sizeof($queueEditor) > 0) { $queueEditor = array_filter($queueEditor, function ($n) { return strtotime($n["date"]) > strtotime(now()) - 5; }); } } - if(!empty($queueEditor)) { - if(sizeof($queueEditor) > 0) { + if (!empty($queueEditor)) { + if (sizeof($queueEditor) > 0) { // on supprime les connexions si la liste (tableau) est mal formatée // en effet parfois la liste n'est pas un tableau multidimensionnel ce qui créé un bug // un code à supprimer sur le long terme @@ -412,12 +419,12 @@ trait LinksOperation if (cache()->has('queue_editor' . $fluidbookId) && !empty($queueEditor)) { $user = User::withoutGlobalScopes()->find(current($queueEditor)["userid"]); $user = $user->firstname . ' ' . $user->lastname; - if(sizeof($queueEditor) === 1) return json_encode(["status" => "available"]); + if (sizeof($queueEditor) === 1) return json_encode(["status" => "available"]); if (current($queueEditor)["token"] === $token) { return json_encode(["status" => "available"]); } else { - return json_encode(["status"=>"unavailable","infos_connection"=>array_merge(["user" => $user,'id'=>current($queueEditor)["userid"]])]); + return json_encode(["status" => "unavailable", "infos_connection" => array_merge(["user" => $user, 'id' => current($queueEditor)["userid"]])]); } } return json_encode(["status" => "available"]); diff --git a/resources/linkeditor-stable/js/linkeditor.accessControl.js b/resources/linkeditor-stable/js/linkeditor.accessControl.js new file mode 100644 index 000000000..10b15c6ab --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.accessControl.js @@ -0,0 +1,82 @@ +function LinkeditorAccessControl(linkeditor) { + this.linkeditor = linkeditor; + this.init(); +} + +LinkeditorAccessControl.prototype = { + init: function () { + this.interval = null + this.token = $("[data-token]").data("token") + this.userID = $("#userID").data('id') + const $this = this + + $(document).on("click", "#connectTo", function(e) { + e.preventDefault() + clearTimeout($this.interval) + $this.intervalConnection("1"); + }) + + $(window).on("unload",function(){ + clearTimeout($this.interval) + sessionStorage.setItem('token', token); + localStorage.setItem('unload', '1'); + }) + + this.interval = setTimeout(() => { $this.intervalConnection() }, 10 ) + }, + + intervalConnection: function(clear = null) { + let unload = localStorage.getItem('unload') + let currentToken = sessionStorage.getItem('token') + const $this = this + + $.ajax({ + method: "POST", + url: '/fluidbook-publication/' + FLUIDBOOK_DATA.id + '/edit/links', + data: {id: FLUIDBOOK_DATA.id, token: currentToken ? currentToken : $this.token, unload: unload, clear: clear} + }).done(function (msg) { + let response = JSON.parse(msg) + if (response.status === "unavailable") { + if ($("#popup-overlay").find('.popup').length === 0) { + $("#popup-overlay").addClass("show") + + let popupName = 'unavailable'; + if(response.infos_connection.id === $this.userID) popupName = 'unavailable-twin-connection'; + $this.linkeditor.popup.open(popupName); + + $this.linkeditor.save.saveIfUnsavedChanges("Sauvegarde après avoir été déconnecté par un autre utilisateur", false, function () { + }); + + // Bloquer les raccourcis clavier + $this.linkeditor.controlKeyFilter(true) + + $("#username").text(response.infos_connection.user) + $("#id").text(response.infos_connection.id) + } + } else { + if ($("#popup-overlay").find('.popup[data-popup^="unavailable"]').length > 0) { + $("#popup-overlay").removeClass("show") + window.linkeditor.popup.close(); + } + $this.linkeditor.controlKeyFilter(false) + window.key.filter = function(event) { + let tagName = (event.target || event.srcElement).tagName; + //let field=tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'; + if (tagName === 'TEXTAREA' && event.keyCode === 13) { + return false; + } + + return true; + } + } + sessionStorage.removeItem('token'); + localStorage.removeItem('unload'); + + $this.interval = setTimeout(() => { + $this.intervalConnection() + }, 2000) + }); + }, +} + +export default LinkeditorAccessControl; diff --git a/resources/linkeditor-stable/js/linkeditor.clipboard.js b/resources/linkeditor-stable/js/linkeditor.clipboard.js new file mode 100644 index 000000000..5de9600e8 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.clipboard.js @@ -0,0 +1,112 @@ +function LinkeditorClipboard(linkeditor) { + this.linkeditor = linkeditor; + this.content = ''; + this.blocked = true; + this._isEmpty = true; + + this.init(); +} + +LinkeditorClipboard.prototype = { + init: function () { + let $this = this; + if (!this.support()) { + return; + } + $(document).on('touchstart click', function () { + $this.checkBlocked(); + return true; + }); + + }, + + checkBlocked: function () { + if (!this.blocked) { + return; + } + this.get(function (c) { + }, true); + }, + + enabled: function () { + return this.support(); + }, + + support: function () { + return !this.linkeditor.utils.isFirefox(); + }, + + empty: function () { + this.set(''); + }, + + get: function (callback, force) { + let $this = this; + if (force || this.enabled()) { + navigator.clipboard.read() + .then(items => { + $this.blocked = false; + items[0].getType('text/html') + .then(blob => { + blob.text() + .then(text => { + if (text.indexOf('') >= 0) { + $this.content = text; + callback($this.content); + } + }) + .catch(err => { + callback(this.content); + }); + }) + .catch(err => { + callback(this.content); + }); + }).catch(err => { + callback(this.content); + }); + } else { + callback(this.content); + } + }, + + isEmpty: function () { + if (!this.enabled()) { + return this.content === ''; + } + return this._isEmpty; + }, + + checkEmpty: function () { + if (!this.enabled()) { + return; + } + let $this = this; + this.get(function (content) { + $this._isEmpty = content === ''; + }); + }, + + set: function (content) { + let $this = this; + this.content = content; + + if (this.enabled()) { + const type = "text/html"; + const blob = new Blob(['' + this.content + ''], {type}); + const data = [new ClipboardItem({[type]: blob})]; + + navigator.clipboard.write(data).then( + () => { + $this.checkEmpty(); + /* success */ + }, + () => { + /* failure */ + } + ); + } + }, +} + +export default LinkeditorClipboard; diff --git a/resources/linkeditor-stable/js/linkeditor.form.js b/resources/linkeditor-stable/js/linkeditor.form.js new file mode 100644 index 000000000..88782a380 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.form.js @@ -0,0 +1,373 @@ +function LinkeditorForm(linkeditor) { + this.linkeditor = linkeditor; + this.loadingFields = {}; + this._maskChangeEvent = false; + this._maskChangeEventTimeout = null; + this._saveFormDataAfterTextChangeTimeout=null; + this.init(); +} + +LinkeditorForm.prototype = { + init: function () { + var $this = this; + + $(document).on('click', '.freefile-file a.upload', function (e) { + var c = $(this).closest('.freefile-file'); + let f = $(c).find('.freefile-file-input'); + if (e.originalEvent.ctrlKey && $(c).find('.freefile-file-input-directory').length > 0) { + f = $(c).find('.freefile-file-input-directory'); + } + f.get(0).click(); + return false; + }); + + $(document).on('change', '.calculation', function () { + var v = $(this).val(); + if (/[0-9\., \-\+\*\/]+/.test(v)) { + try { + v = eval(v.replace(/,/g, '.')); + console.log(v); + if (isNaN(v)) { + return; + } + } catch (e) { + return; + } + } + $(this).val(v); + }); + + $(document).on('change', '#linkeditor-panel-form [name="type"]', function () { + console.log('change type'); + if ($this._maskChangeEvent) { + return; + } + $this.saveFormDataInLink(); + $this.updateLinkForm(); + }); + + $(document).on('change keyup keydown', '#linkeditor-panel-form [name="uid"]', function () { + let v = $(this).val(); + v = v.replace(/[^a-zA-Z0-9_]*/g, ''); + if (v !== $(this).val()) { + $(this).val(v); + } + return true; + }); + + setInterval(function () { + if ($(document.activeElement).is('.sp-input')) { + var v = $(document.activeElement).val(); + let main = $(document.activeElement).closest('.form-group').find('.colorpicker'); + $(main).val(v); + $(main).trigger('change'); + + } + }, 250); + + $(document).on('change keyup', "#linkeditor-panel-form input,#linkeditor-panel-form select,#linkeditor-panel-form textarea", function (e) { + if ($this._maskChangeEvent) { + return; + } + + let callback = function () { + console.log('Save form data in link after change'); + $this.saveFormDataInLink(); + } + + clearTimeout($this._saveFormDataAfterTextChangeTimeout); + if (e.type === 'change') { + callback(); + } else { + $this._saveFormDataAfterTextChangeTimeout = setTimeout(callback, 500); + } + }); + + + $(document).on('change', ".freefile-file-input, .freefile-file-input-directory", function () { + var form = $('#linkupload').clone(); + + var id = 'linkupload_' + Math.round(Math.random() * 10000000); + $(form).attr('id', id); + $(this).attr('form', id); + $('body').append(form); + + var parent = $(this).closest('.freefile-file'); + var tf = $(parent).find('.freefile-text-input'); + let tfname = $(tf).attr('name'); + let uid = $this.getCurrentFormUID(); + $this.loadingFields[uid + '_' + tfname] = true; + + $(parent).addClass('loading'); + var f = $(form).ajaxSubmit({dataType: 'json'}); + var xhr = f.data('jqxhr'); + xhr.done(function (data) { + if ($this.getCurrentFormUID() === uid) { + let tf = $("#linkeditor-panel-form").find('.freefile-text-input[name="' + tfname + '"]'); + tf.val(data[0]); + tf.trigger('change'); + $(tf).closest('.freefile-file').removeClass('loading'); + $this.saveFormDataInLink(); + } else { + let linkData = {}; + linkData[tfname] = data[0]; + $this.linkeditor.links.updateLinkData(uid, linkData, true); + $this.linkeditor.hasChanged(); + } + $this.loadingFields[uid + '_' + tfname] = false; + setTimeout(function () { + $this.linkeditor.save.saveIfUnsavedChanges(TRANSLATIONS.upload_save_message, false, function () { + + }); + }, 500); + + }); + }); + }, + + getCurrentFormUID: function () { + let uid = $("#linkeditor-panel-form").find('[name="uid"]'); + if (uid.length > 0) { + return uid.val(); + } + return false; + }, + + saveFormDataInLink: function () { + var $this = this; + var form = $("#linkeditor-panel-form form"); + if ($(form).length === 0) { + return; + } + + this.maskChangeEvent(); + var link = $(form).data('link'); + var data = this.serializeForm($(form)); + var ignore = ['_method', '_referrer', '_token', 'http_referrer']; + var map = {y: 'top', x: 'left', w: 'width', h: 'height'}; + + var savedData = {}; + var changed = false; + $.each(data, function (k, v) { + if (ignore.indexOf(k) >= 0) { + return; + } + let key = k; + if (map[k] !== undefined && map[k] !== null) { + key = map[k]; + } + let currentValue = $this.normalizeFormValue(key, $(link).attr('fb-' + key)); + v = $this.normalizeFormValue(key, v); + + if (v !== currentValue) { + savedData[key] = v; + $(link).attr('fb-' + key, v); + changed = true; + } + }); + if (changed) { + $(link).attr('fb-update', 1); + this.linkeditor.links.updateLinkData($(link).attr('fb-origuid'), savedData); + this.linkeditor.links.setLastSelectedLink(link); + this.linkeditor.hasChanged(); + } + this.unmaskChangeEvent(); + }, + + + serializeForm: function (form) { + let data = $(form).serializeArray(); + let res = {}; + $.each(data, function (k, v) { + res[v.name] = v.value; + }); + return res; + }, + + normalizeFormValue: function (k, value) { + if (value === undefined || value === null) { + value = ''; + } + var number = ['top', 'left', 'width', 'height', 'rot', 'x', 'y', 'h', 'w']; + var bool = ['display_area']; + var integers = ['zindex']; + if (k === 'zindex' && (value === undefined || value === null || value === '')) { + value = -1; + } + + + if (integers.indexOf(k) >= 0) { + value = parseInt(value); + if (isNaN(value)) { + value = 0; + } + } else if (number.indexOf(k) >= 0) { + value = parseFloat(value); + if (isNaN(value)) { + value = 0; + } + value = this.linkeditor.utils.roundDimension(value); + } else if (bool.indexOf(k) >= 0) { + value = (value === true || value === 'true' || value === "1" || value === 1) ? '1' : '0'; + } + + value = value.toString() + value = value.replace(new RegExp("\r\n", 'g'), "\n"); + value = value.replace(new RegExp("\r", 'g'), "\n"); + value = value.replace(new RegExp("\n+", 'g'), "\n"); + return value; + }, + + focusAndSelectDestinationField: function () { + var f = $('#linkeditor-panel-form [name="to"]'); + if (f.length === 0) { + return; + } + if (!f.is('input,textarea')) { + return; + } + f = f.get(0); + + f.focus(); + f.select(); + setTimeout(function () { + f.select(); + }, 1); + }, + + emptyForm: function () { + $('#linkeditor-panel-form .select2_from_array').each(function () { + $(this).select2('close'); + $(this).select2('destroy'); + }); + $('#linkeditor-panel-form').html(''); + }, + + updateLinkForm: function () { + var links = $('.link.selected:not(.pendingCreate)'); + if (links.length != 1) { + this.emptyForm(); + return; + } + + this.emptyForm(); + var container = $('#linkeditor-panel-form'); + var link = $(links).eq(0); + var type = $(link).attr('fb-type'); + var form = $("#linkeditor-form-template-" + type).clone(false); + + $(form).attr('id', null); + $(form).data('link', link); + $(form).find('.init-tooltip').removeClass('init-tooltip'); + this.updateFormData(form); + container.append(form); + this.linkeditor.initTooltips(); + + this.initSelect2(); + this.initSpectrum(); + + if ($(link).is('.new')) { + this.focusAndSelectDestinationField(); + } + }, + + updateFormData: function (form) { + var $this = this; + try { + if (form === undefined) { + form = $('#linkeditor-panel-form form'); + if (form.length === 0) { + return; + } + } + var link = $(form).data('link'); + if (link === undefined || link === null) { + return; + } + this.lastSelectedLink = link; + } catch (e) { + return; + } + + let linkData = this.linkeditor.links.getLinkData(link); + let uid = linkData.uid; + var map = {top: 'y', left: 'x', width: 'w', height: 'h'}; + + this.maskChangeEvent(); + $.each(linkData, function (k, v) { + if (map[k] !== undefined) { + k = map[k]; + } + v = $this.normalizeFormValue(k, v); + if ($(form).find('[type="checkbox"][name=' + k + ']').length > 0) { + $(form).find('[type="checkbox"][name=' + k + ']').prop('checked', v === '1'); + } else { + $(form).find('[name=' + k + ']').val(v); + } + + if ($this.loadingFields[uid + '_' + k] === true) { + $(form).find('[name=' + k + ']').closest('.freefile-file').addClass('loading'); + } + }); + this.unmaskChangeEvent(); + }, + + + maskChangeEvent: function () { + this._maskChangeEvent = true; + }, + + unmaskChangeEvent: function () { + var $this = this; + clearTimeout(this._maskChangeEventTimeout); + if (this._maskChangeEvent === false) { + return; + } + this._maskChangeEventTimeout = setTimeout(function () { + $this._maskChangeEvent = false; + }, 100); + }, + + + initSelect2: function () { + // trigger select2 for each untriggered select2 box + $('#linkeditor-panel-form .select2_from_array:not(.init)').each(function (i, obj) { + var options = { + theme: "bootstrap", + }; + if ($(this).is('[data-allow-html="1"]')) { + options.escapeMarkup = function (m) { + return m; + } + } + $(this).addClass('init') + $(this).select2(options); + }); + }, + + initSpectrum: function () { + $("#linkeditor-panel-form .colorpicker:not(.init)").each(function () { + var t = $(this); + let s = $(this).spectrum({ + preferredFormat: 'hex3', + showAlpha: true, + allowEmpty: $(t).attr('data-allow-empty') === 'true', + showInput: true, + showInitial: true, + showButtons: false, + flat: true, + move: function (color) { + t.val(color.toString()); + $(t).trigger('change'); + }, + dragend: function (color) { + t.val(color.toString()); + $(t).trigger('change'); + } + }); + $(this).addClass('init'); + }); + } + +}; +export default LinkeditorForm; diff --git a/resources/linkeditor-stable/js/linkeditor.js b/resources/linkeditor-stable/js/linkeditor.js new file mode 100644 index 000000000..7eece39a1 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.js @@ -0,0 +1,612 @@ +import tippy from 'tippy.js'; +import 'tippy.js/dist/tippy.css'; + +import Noty from "noty"; +import 'noty/lib/noty.css'; +import 'noty/lib/themes/mint.css'; + +import GrahamScan from "@lucio/graham-scan"; + +import LinkeditorLinks from './linkeditor.links'; +import LinkeditorLoader from './linkeditor.loader'; +import LinkeditorResize from './linkeditor.resize'; +import LinkeditorRulers from './linkeditor.rulers'; +import LinkeditorToolbar from './linkeditor.toolbar'; +import LinkeditorUtils from './linkeditor.utils'; +import LinkeditorZoom from './linkeditor.zoom'; +import LinkeditorSave from './linkeditor.save'; +import LinkeditorSettings from './linkeditor.settings'; +import LinkeditorPanels from './linkeditor.panels'; +import LinkeditorForm from './linkeditor.form'; +import LinkeditorVersions from './linkeditor.versions'; +import LinkeditorPopup from './linkeditor.popup'; +import LinkeditorLayers from "./linkeditor.layers"; +import LinkeditorUndo from './linkeditor.undo'; +import LinkeditorClipboard from './linkeditor.clipboard'; +import LinkeditorAccessControl from './linkeditor.accessControl'; + +window.$ = window.jQuery = require('jquery'); +window.key = require('keymaster-reloaded'); +window.tippy = tippy; +window.Noty = Noty; +window.GrahamScan = GrahamScan; + +window.key.filter = function (event) { + return keyfilter(event); +}; + +function keyfilter(event, disable = false) { + if(disable) { + return false + } + let tagName = (event.target || event.srcElement).tagName; + //let field=tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'; + if (tagName === 'TEXTAREA' && event.keyCode === 13) { + return false; + } + + return true; +} + +require('jquery.scrollto'); +require('jquery-contextmenu'); +import 'jquery-contextmenu/dist/jquery.contextMenu.css'; + +require('jquery-form'); +require('spectrum-colorpicker'); +import 'spectrum-colorpicker/spectrum.css'; + +require('select2'); +import 'select2/dist/css/select2.css'; +import 'select2-bootstrap-theme/dist/select2-bootstrap.css'; +import * as noty from "noty"; + +window.MD5 = require("crypto-js/md5"); + +$.ajaxSetup({ + headers: { + 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') + } +}); + +function LinkEditor() { + this.mobileFirst = FLUIDBOOK_DATA.settings.mobileNavigationType === 'mobilefirst'; + this.single = ['mobilefirst', 'portrait'].indexOf(FLUIDBOOK_DATA.settings.mobileNavigationType) >= 0 || FLUIDBOOK_DATA.settings.pages <= 1; + + this.pw = FLUIDBOOK_DATA.settings.width; + this.ph = FLUIDBOOK_DATA.settings.height; + this.fw = this.pw * (this.single ? 1 : 2); + this.fh = this.ph; + this.fs = 1; + this.mx = 0; + this.my = 0; + this.bookSurface = this.pw * this.ph; + + this.fluidbookRect = null; + this.canvasRect = null; + this.editorRect = null; + this.currentPage = -1; + this.currentNumericPage = 1; + this.maskHashEvent = false; + this.windowHasFocus = true; + + this.rightClick = false; + + this.dimensionProperties = ['left', 'top', 'width', 'height']; + + this.init(); +} + +LinkEditor.prototype = { + init: function () { + var $this = this; + + this.utils = new LinkeditorUtils(this); + this.clipboard = new LinkeditorClipboard(this); + this.toolbar = new LinkeditorToolbar(this); + this.resize = new LinkeditorResize(this); + this.rulers = new LinkeditorRulers(this); + this.zoom = new LinkeditorZoom(this); + this.links = new LinkeditorLinks(this); + this.loader = new LinkeditorLoader(this); + this.save = new LinkeditorSave(this); + this.panels = new LinkeditorPanels(this); + this.form = new LinkeditorForm(this); + this.settings = new LinkeditorSettings(this); + this.versions = new LinkeditorVersions(this); + this.layers = new LinkeditorLayers(this); + this.popup = new LinkeditorPopup(this); + this.undo = new LinkeditorUndo(this); + this.accessControl = new LinkeditorAccessControl(this); + + this.initEvents(); + this.initIcons(); + this.panels.init(); + this.zoom.reset(); + }, + + initIcons: function () { + $("[data-icon]:not(.init-icon)").each(function () { + $(this).append(getSpriteIcon('linkeditor-' + $(this).data('icon'))).addClass('init-icon'); + }); + }, + + initTooltips: function () { + $('[data-tooltip]:not(.init-tooltip)').each(function () { + let i = tippy($(this).get(0), {content: $(this).data('tooltip')}); + $(this).addClass('init-tooltip'); + $(this).data('tippyinstance', i) + }); + }, + + initEvents: function () { + var $this = this; + + $(document).on('mousedown', '*', function (e) { + $this.rightClick = e.which !== 1; + return true; + }); + + $(document).on('mouseup', '*', function (e) { + if ($this.rightClick) { + setTimeout(function () { + $this.rightClick = false; + }, 100); + } + $this.rightClick = false; + return true; + }); + + $(window).on('hashchange', function () { + if ($this.maskHashEvent) { + return; + } + $this.changePage(); + }); + + $(window).on('blur', function () { + $this.windowHasFocus = false; + }); + + $(window).on('focus', function () { + if (!$this.windowHasFocus) { + $this.windowHasFocus = true; + $this.clipboard.checkEmpty(); + $this.resetKeyModifiers(); + } + }); + + key('esc', function () { + $this.resetKeyModifiers(); + if ($this.popup.hasOpenPopup()) { + $this.popup.close(); + return false; + } + return true; + }); + + + $(window).on('keydown', function (e) { + if ($this.utils.isfocusOnFormItem()) { + return true; + } + if (e.keyCode == 16) { + $("#linkeditor-main").addClass('selection'); + } else if (e.keyCode == 32) { + if (!$this.mobileFirst) { + $("#linkeditor-main").addClass('grab'); + } + } else if (e.keyCode == 18) { + $("#linkeditor-main").addClass('duplicate'); + + } else if (e.key == '>' || e.keyCode === 60 || e.keyCode === 226 || e.key==='<' || e.keyCode===188) { + $("#linkeditor-main").addClass('polygon'); + } + $this.rulers.moveRuler(); + return false; + }); + + $(window).on('keyup', function (e) { + if (e.keyCode == 16) { + $("#linkeditor-main").removeClass('selection'); + } else if (e.keyCode == 32) { + $this.zoom.resetZoomDrag(); + } else if (e.keyCode == 18) { + $("#linkeditor-main").removeClass('duplicate'); + } else if (e.key == '>' || e.keyCode === 60 || e.keyCode === 226 || e.key==='<' || e.keyCode===188) { + $("#linkeditor-main").removeClass('polygon'); + $this.links.closePolygonShape(); + } + $this.rulers.moveRuler(); + if ($this.utils.isfocusOnFormItem()) { + return true; + } + return false; + }); + + $(document).on('mousedown', "#linkeditor-editor", function (e) { + $this.setMouseCoordinates(e); + if ($this.rightClick) { + return true; + } + var deselectAll = true; + + if ($('#linkeditor-main').hasClass('selection')) { + $this.links.startRectSelection(); + return false; + } else if ($('#linkeditor-main').hasClass('duplicate')) { + $this.links.duplicateLinkClick(); + return false; + } else if ($('#linkeditor-main').hasClass('grab')) { + $this.zoom.zoomdragging = { + x: $this.mx, + y: $this.my, + scrollX: $("#linkeditor-canvas").scrollLeft(), + scrollY: $("#linkeditor-canvas").scrollTop() + }; + $(this).addClass('grabbing'); + } else { + // Check if the user is not clicking the scrollbar + if ($this.mx < $this.canvasRect.right - 20 && $this.my < $this.canvasRect.bottom - 20) { + $this.zoom.resetZoomDrag(); + $this.links.mouseDown(); + deselectAll = false; + } + } + if (deselectAll) { + $this.form.saveFormDataInLink(); + $this.links.deselectAllLinks(); + } + }); + + $(window).on('mousemove', function (e) { + $this.setMouseCoordinates(e); + if ($this.rightClick) { + return true; + } + $this.updateMousePosition(e); + }); + + $(window).on('mouseup', function (e) { + $this.setMouseCoordinates(e); + if ($this.rightClick) { + return true; + } + $this.panels.mouseup(); + $this.zoom.mouseUp(); + $this.rulers.mouseUp(); + $this.links.mouseUp(); + }); + + this.links.initEvents(); + + this.resize.resize(); + this.changePage(); + }, + + controlKeyFilter: function (disabled) { + window.key.filter = function (event) { + return keyfilter(event, disabled); + }; + }, + + resetKeyModifiers: function () { + $("#linkeditor-main").removeClass('selection').removeClass('grab').removeClass('duplicate'); + }, + + runAction: function (act, args) { + if (args === undefined) { + args = []; + } + if (typeof args === 'string') { + args = [args]; + } + var a = act.split('.'); + var o = this; + let po = this; + for (let i in a) { + po = o; + o = o[a[i]]; + } + try { + return o.apply(po, args); + } catch (e) { + console.log(e); + console.error('Error while calling ' + act, args); + } + }, + + firstPage: function () { + this.changePage(1); + }, + + nextPage: function () { + this.changePage(this.currentNumericPage + (this.single ? 1 : 2)); + }, + + previousPage: function () { + this.changePage(this.currentNumericPage - (this.single ? 1 : 2)); + }, + + lastPage: function () { + this.changePage(FLUIDBOOK_DATA.settings.pages); + }, + + focusPageField: function () { + let i = $("#linkeditor-page-field input").get(0); + i.focus(); + i.select(); + }, + + hasChanged: function (push) { + if (push === undefined) { + push = true; + } + this.save.hasChanged(); + if (push === true) { + this.undo.pushState(); + } + this.layers.update(); + this.updateFBElements(true); + }, + + updateFBElements: function (force) { + let $this = this; + requestAnimationFrame(function () { + $this._updateFBElements(force); + }); + }, + + _updateFBElements: function (force) { + let $this = this; + let selector = '[fb-ref]'; + if (force !== true) { + selector += '[fb-update="1"]'; + } + + $(selector).each(function () { + let e = $(this); + let rect = $(this).attr('fb-ref'); + let css = {}; + $.each($this.dimensionProperties, function (k, dim) { + if ($(e).is('[fb-' + dim + ']')) { + let v = parseFloat($(e).attr('fb-' + dim)); + if (dim === 'width' || dim === 'height') { + css[dim] = (v * ($this.fs * $this.zoom.zoom)) + 1; + } else { + css[dim] = $this.fluidbookTo(v, dim, rect); + } + } + }); + $(e).css(css).attr('fb-update', '0'); + }); + this.form.updateFormData(); + }, + + getCurrentPageHeight: function () { + if (this.mobileFirst) { + return FLUIDBOOK_DATA.page_dimensions[this.currentPage][1]; + } + return FLUIDBOOK_DATA.height; + }, + + fluidbookTo: function (dim, name, rect) { + switch (rect) { + case 'editor': + rect = this.editorRect; + break; + case 'canvas': + rect = this.canvasRect; + break; + } + return this.fluidbookToGlobal(dim, name) - rect[name]; + }, + + + updateMousePosition: function (e) { + if (e !== undefined) { + this.setMouseCoordinates(e); + } + if (this.rightClick) { + return; + } + + this.panels.moveHandle(); + this.rulers.updateMousePositionRulers(); + this.rulers.moveRuler(); + this.links.moveDragLink(); + this.links.moveResizeLink(); + this.links.movePolygonPoint(); + this.zoom.updateMousePosition(); + }, + + setMouseCoordinates: function (e) { + this.mx = e.pageX; + this.my = e.pageY; + }, + + changePage: function (page) { + // Save and deselect all links + this.links.deselectAllLinks(); + this.form.emptyForm(); + + var $this = this; + this.maskHashEvent = true; + setTimeout(function () { + $this.maskHashEvent = false; + }, 500); + var formerPage = this.currentPage; + var wasSpecialPage = this.utils.isSpecialPage(formerPage); + + if (page === undefined) { + let h = window.location.hash; + if (h.length === 0) { + page = 0; + } else { + page = window.location.hash.substring(1); + } + } + + let normPage = this.utils.normalizePage(page); + let isSpecial = this.utils.isSpecialPage(normPage); + + // Consider than clicking the special page act as a toggle + if (normPage === this.currentPage && isSpecial) { + normPage = this.currentNumericPage; + isSpecial = false; + } + + // Keep the value of the numeric page to be able to use arrows to change page even from a special page + if (!isSpecial) { + this.currentNumericPage = normPage; + } + + // Set icon of special page active + $("[data-special]").removeClass('active'); + if (isSpecial) { + $("[data-special=" + normPage + "]").addClass('active'); + } + + if (normPage === this.currentPage) { + return; + } + this.currentPage = normPage; + + window.location.hash = '#' + this.currentPage; + this.clearLinksAndRulers(); + this.loader.loadPage(this.currentPage, 'left'); + if (!this.single && !isSpecial) { + this.loader.loadPage(this.currentPage + 1, 'right'); + } + $("#linkeditor-page-field input").val(this.currentNumericPage); + this.resize.resize(); + if (this.mobileFirst || isSpecial || wasSpecialPage) { + this.zoom.reset(); + } + this.undo.updateIconsStates(); + this.loader.preloadPages(); + }, + + getCurrentPages: function (page) { + if (page === undefined) { + page = this.currentPage; + } else { + page = this.utils.normalizePage(page); + } + if (this.single || this.utils.isSpecialPage(page)) { + return [page]; + } + if (page % 2 === 1) { + page--; + } + return [page, page + 1]; + }, + + toggleWhiteOverlay: function () { + $("#linkeditor-fluidbook").toggleClass('white-overlay'); + if ($("#linkeditor-fluidbook").hasClass('white-overlay')) { + $('[data-action="toggleWhiteOverlay"]').addClass('active'); + } else { + $('[data-action="toggleWhiteOverlay"]').removeClass('active'); + } + }, + + togglePDFThumbnails: function () { + this.loader.togglePagesSource(); + if (this.loader.pagesSource === 'thumbnails') { + $('[data-action="togglePDFThumbnails"]').addClass('active'); + } else { + $('[data-action="togglePDFThumbnails"]').removeClass('active'); + } + }, + + openFluidbook: function () { + window.open('/fluidbook-publication/preview/' + FLUIDBOOK_DATA.id + '_' + FLUIDBOOK_DATA.hash + '/#/page/' + this.currentPage); + }, + +// Convert global coordinates to fluidbook ones + globalToFluidbook: function (x, y, onePage) { + let res = this._globalTo(x, y, this.fluidbookRect, 1 / (this.fs * this.zoom.zoom)); + if (onePage) { + res.xside = 'left'; + if (!this.single && res.x >= this.pw) { + res.xside = 'right'; + res.x -= this.pw; + } + } + return res; + }, + + fluidbookToGlobal: function (dim, name) { + return this.fluidbookRect[name] + (dim * this.fs * this.zoom.zoom); + }, + + globalToCanvas: function (x, y) { + return this._globalTo(x, y, this.canvasRect, 1); + }, + + globalToEditor: function (x, y) { + return this._globalTo(x, y, this.editorRect, 1); + }, + + _globalTo: function (x, y, rect, multi) { + return {x: multi * (x - rect.x), y: multi * (y - rect.y)}; + }, + + clearLinksAndRulers: function () { + this.rulers.clear(); + this.links.clear(); + }, + + notification: function (text, type, timeout) { + if (timeout === undefined) { + timeout = 5000; + } + if (type === undefined) { + type = 'success'; + } + new Noty({ + type: type, + text: text, + timeout: timeout, + }).show(); + }, + + getCurrentPage: function () { + return this.currentPage; + }, + + displayLoader: function () { + $("#loader").show(); + }, + + hideLoader() { + $("#loader").hide(); + } + +} + + +$(function () { + window.linkeditor = new LinkEditor(); +}); + +(function (old) { + $.fn.attr = function () { + if (arguments.length === 0) { + if (this.length === 0) { + return null; + } + + var obj = {}; + $.each(this[0].attributes, function () { + if (this.specified) { + obj[this.name] = this.value; + } + }); + return obj; + } + + return old.apply(this, arguments); + }; +})($.fn.attr); diff --git a/resources/linkeditor-stable/js/linkeditor.layers.js b/resources/linkeditor-stable/js/linkeditor.layers.js new file mode 100644 index 000000000..ddbca2148 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.layers.js @@ -0,0 +1,128 @@ +function LinkeditorLayers(linkeditor) { + this.linkeditor = linkeditor; +} + +LinkeditorLayers.prototype = { + init: function () { + var $this = this; + + this.container = $("#linkeditor-panel-layers"); + this.maskCheckEvents = false; + + $(document).on('click', '#linkeditor-panel-layers a.lock', function () { + let uid = $(this).closest('.layer').find('input').attr('name'); + $this.linkeditor.links.locks.toggleLock(uid); + return false; + }); + + $(document).on('click', "#linkeditor-panel-layers label", function (e) { + if ($this.maskCheckEvents) { + return false; + } + let uid = $(this).find('input').attr('name'); + let checked = $(this).find('input').prop('checked'); + var link = $('#linkeditor-links [fb-uid="' + uid + '"]'); + + if (!e.ctrlKey) { + $this.linkeditor.links.deselectAllLinks(); + $this.linkeditor.links.selectLink(link); + } else { + if (checked) { + $this.linkeditor.links.deselectLink(link); + } else { + $this.linkeditor.links.selectLink(link); + } + } + + $this.linkeditor.form.updateLinkForm(); + return false; + }); + + $(document).on('click', '#linkeditor-panel-layers label span.uid', function () { + navigator.clipboard.writeText($(this).attr('fb-uid')); + let tippy = $(this).data('tippyinstance'); + console.log(tippy); + tippy.setContent(TRANSLATIONS.id_copied); + tippy.show(); + return false; + }); + + this.update(); + }, + + update: function () { + if (this.container === undefined) { + return; + } + if (!this.container.hasClass('open')) { + return; + } + var $this = this; + this.container.html(''); + var layers = []; + $('#linkeditor-links .link:not(.pendingCreate)').each(function () { + let type = $(this).attr('fb-type'); + let dest = $(this).attr('fb-to'); + if (dest === '') { + dest = '' + TRANSLATIONS.empty + ''; + } + var l = '
'; + l += ''; + l += ''; + l += '
'; + let d = parseInt($(this).attr('fb-calc-depth')); + var m = 1; + if (d >= 30 && d < 50) { + m = 10; + } + let level = Math.floor((m * d) / 10) / m; + layers.push({ + level: level, + zindex: parseInt($(this).attr('fb-calc-zindex')), + html: l + }); + }); + layers.sort(function (a, b) { + return b.zindex - a.zindex; + }); + var seenLevels = {}; + $.each(layers, function (k, v) { + if (seenLevels[v.level] === undefined) { + seenLevels[v.level] = true; + $this.container.append('

' + TRANSLATIONS.level + ' #' + v.level + '

'); + } + $this.container.append(v.html); + }); + + + this.updateSelection(); + this.linkeditor.initTooltips(); + this.linkeditor.initIcons(); + }, + + updateSelection() { + + if (this.container === undefined || this.container.is(':hidden')) { + return; + } + var $this = this; + this.maskCheckEvents = true; + setTimeout(function () { + $this.maskCheckEvents = false; + }, 100); + + $('#linkeditor-links .link').each(function () { + let checkbox = $this.container.find('input[name=' + $(this).attr('fb-uid') + ']'); + checkbox.prop('checked', $(this).is('.selected')); + }); + }, + + resize: function () { + + }, +}; +export default LinkeditorLayers; diff --git a/resources/linkeditor-stable/js/linkeditor.links.js b/resources/linkeditor-stable/js/linkeditor.links.js new file mode 100644 index 000000000..524023e06 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.links.js @@ -0,0 +1,1548 @@ +import LinkeditorLinksLock from './linkeditor.links.lock'; + +var LinkeditorLinks = function (linkeditor) { + this.linkeditor = linkeditor; + + this.dragLinkPos = null; + this.resizeLinkPos = null; + this.movePolygonPointPos = null; + + this.magnetValuesX = []; + this.magnetValuesY = []; + + this.lastSelectedLink = null; + this.lastSelectedLinkData = {'width': 100, 'height': 100, 'to': '', type: '2', target: '_blank'}; + + this.contextMenuPosition = null; + + this.rectSelection = null; + + this.dropTypes = [4, 6, 7, 12, 15, 16, 17, 18, 25, 30, 31]; + + this.locks = new LinkeditorLinksLock(linkeditor); + + this.init(); +} + +LinkeditorLinks.prototype = { + init: function () { + }, + + initEvents: function () { + let $this = this; + $(document).on('mousedown', '.link .corners div', function (e) { + if ($this.linkeditor.rightClick) { + return true; + } + e.preventDefault(); + e.stopPropagation(); + $this.deselectAllLinks(); + let link = $(this).closest('.link'); + $this.selectLink(link); + if ($(this).hasClass('poly')) { + $this.startMovePolygonPoint($(this).data('point')); + } else { + $this.startResizeLink($(this).attr('class')); + } + }); + + $(document).on('mousedown', '.link', function (e) { + if ($this.linkeditor.rightClick) { + return true; + } + e.preventDefault(); + e.stopPropagation(); + var selectLink = false; + var deselectAll = false; + if (!$(this).hasClass('selected')) { + if (!e.ctrlKey) { + deselectAll = true; + } + selectLink = true; + } else { + if (e.ctrlKey) { + selectLink = false; + } else { + selectLink = true; + } + } + if (deselectAll) { + $this.deselectAllLinks(); + } + if (selectLink) { + $this.selectLink($(this)); + $this.startDragLink(); + } else { + $this.deselectLink($(this)); + } + + return false; + }); + + + this.key('ctrl+alt+shift+f', function () { + $this.fixDriftedLinks(); + }); + this.key('ctrl+a', function () { + $this.selectAllExceptLocked(); + return false; + }); + this.key('ctrl+c', function () { + $this.copy(); + return false; + }); + this.key('ctrl+o', function () { + $this.locks.lockSelection(); + return false; + }); + this.key('ctrl+x', function () { + $this.cut(); + return false; + }); + this.key('ctrl+v', function () { + $this.paste(); + return false; + }); + this.key('del', function () { + $this.deleteSelection(); + }); + this.key('left', function () { + $this.offsetSelectedLinks('left', -1); + }); + this.key('ctrl+left', function () { + $this.offsetSelectedLinks('left', -10); + }); + this.key('right', function () { + $this.offsetSelectedLinks('left', 1); + }); + this.key('ctrl+right', function () { + $this.offsetSelectedLinks('left', 10); + }); + this.key('up', function () { + $this.offsetSelectedLinks('top', -1); + }); + this.key('ctrl+up', function () { + $this.offsetSelectedLinks('top', -10); + }); + this.key('down', function () { + $this.offsetSelectedLinks('top', 1); + }); + this.key('ctrl+down', function () { + $this.offsetSelectedLinks('top', 10); + }); + this.key('pageup', function () { + $this.selectPreviousLink(); + }); + this.key('pagedown', function () { + $this.selectNextLink(); + }); + this.key('ctrl+l', function () { + $this.openImageLink(); + return false; + }); + this.key('enter', function () { + if ($(document.activeElement).is('input[name="to"]')) { + $this.selectNextLink(); + return false; + } + }); + + var commonDragAndDropEvent = function (e) { + e.originalEvent.dataTransfer.dropEffect = "copy"; + e.preventDefault(); + } + $(document).on('dragenter', '#linkeditor-main', function (e) { + // Prevent canvas being scrolled at the begining of the drag + $("#linkeditor-canvas").addClass('noscroll'); + setTimeout(function () { + $("#linkeditor-canvas").removeClass('noscroll'); + }, 2000); + commonDragAndDropEvent(e); + }).on('dragover', '#linkeditor-main', function (e) { + $('#linkeditor-main').addClass('dropfile'); + commonDragAndDropEvent(e); + }).on('dragleave', '#linkeditor-main', function (e) { + $('#linkeditor-main').removeClass('dropfile'); + commonDragAndDropEvent(e); + }); + + $(document).on('dragenter dragover', '.link.dropfile', function (e) { + commonDragAndDropEvent(e); + $(this).addClass('dragging'); + }); + $(document).on('dragleave', '.link.dropfile', function (e) { + commonDragAndDropEvent(e); + $(this).removeClass('dragging'); + }); + $(document).on('drop', '.link.dropfile', function (e) { + let link = this; + $this.deselectAllLinks(); + + commonDragAndDropEvent(e); + let uid = $(this).attr('fb-uid'); + var fd = new FormData(); + fd.append('entity', 'App\\Models\\FluidbookPublication'); + fd.append('entry_id', FLUIDBOOK_DATA.id); + fd.append('attribute', 'linksAssets'); + fd.append('file', e.originalEvent.dataTransfer.files[0]); + $.ajax({ + url: '/filesorurlupload', + type: 'post', + data: fd, + contentType: false, + processData: false, + dataType: 'json', + success: function (response) { + $(link).attr('fb-to', response[0]); + $this.selectLink(link); + LINKS[uid].to = response[0]; + $this.linkeditor.hasChanged(true); + } + }); + $(this).removeClass('dragging'); + $('#linkeditor-main').removeClass('dropfile'); + }); + + + $.contextMenu({ + selector: '#linkeditor-canvas,.link,#linkeditor-panel-layers, #linkeditor-panel-layers label', events: { + show: function (e) { + $this.contextMenuPosition = {x: $this.linkeditor.mx, y: $this.linkeditor.my}; + }, preShow: function (e) { + if ($(e).is('.link:not(.selected)')) { + $this.deselectAllLinks(); + $this.selectLink(e); + } + if ($(e).is('#linkeditor-panel-layers label')) { + let input = $(e).find('input'); + if (!input.prop('checked')) { + $this.deselectAllLinks(); + $this.selectLink($('.link[fb-uid=' + input.attr('name') + ']')); + } + } + }, + }, build: function ($triggerElement, e) { + var res = { + callback: function () { + + } + }; + var selection = $(".link.selected"); + var multiple = selection.length > 1; + var hasSelection = selection.length > 0; + var hasClipboard = !$this.linkeditor.clipboard.isEmpty(); + + res.items = { + 'select_all': { + isHtmlName: true, name: TRANSLATIONS.select_all + ' Ctrl+A', callback: function () { + $this.selectAllExceptLocked(); + }, + } + }; + if (hasSelection && !multiple) { + res.items = $.extend(res.items, { + 'sep_link': '---------', 'copy_link_id': { + isHtmlName: true, name: TRANSLATIONS.copy_link_id, callback: function () { + navigator.clipboard.writeText(selection.attr('fb-uid')); + }, + } + }); + if (CAN_CONTAIN_LINKS[parseInt($(selection).attr('fb-type'))] !== undefined) { + res.items.image_link = { + isHtmlName: true, + name: TRANSLATIONS.edit_image_link + ' Ctrl+L', + callback: function () { + $this.openImageLink(); + } + }; + } + } + if (hasSelection) { + res.items.sep_lock = '---------'; + res.items.lock = { + isHtmlName: true, name: TRANSLATIONS.lock + ' Ctrl+O', callback: function () { + $this.locks.lockSelection(); + }, + }; + } + if (hasSelection || hasClipboard) { + res.items.sep_clipboard = '---------'; + if (hasSelection) { + res.items.copy_to_clipboad = { + isHtmlName: true, name: TRANSLATIONS.copy + ' Ctrl+C', callback: function () { + $this.copy(); + }, + }; + res.items.cut_to_clipboad = { + isHtmlName: true, name: TRANSLATIONS.cut + ' Ctrl+X', callback: function () { + $this.cut(); + }, + }; + } + if (hasClipboard) { + res.items.paste_here = { + isHtmlName: true, name: TRANSLATIONS.paste_here, callback: function () { + $this.paste($this.contextMenuPosition); + }, + }; + res.items.paste_in_place = { + isHtmlName: true, + name: TRANSLATIONS.paste_in_place + ' Ctrl+V', + callback: function () { + $this.paste(); + }, + }; + res.items.paste_on_left = { + isHtmlName: true, + name: TRANSLATIONS.paste_on_left, + callback: function () { + $this.paste('left'); + }, + }; + res.items.paste_on_right = { + isHtmlName: true, + name: TRANSLATIONS.paste_on_right, + callback: function () { + $this.paste('right'); + }, + }; + } + } + if (hasSelection && !multiple) { + res.items = $.extend(res.items, { + 'sep_extends': '---------', 'cover_1': { + isHtmlName: true, name: TRANSLATIONS.cover_page_1, callback: function () { + $this.coverPage(1, false); + }, + }, 'cover_0': { + isHtmlName: true, name: TRANSLATIONS.cover_page_0, callback: function () { + $this.coverPage(0, false); + }, + }, + }); + if (!$this.linkeditor.single) { + res.items = $.extend(res.items, { + 'cover_double_1': { + isHtmlName: true, name: TRANSLATIONS.cover_doublepage_1, callback: function () { + $this.coverPage(1, true); + }, + }, 'cover_double_0': { + isHtmlName: true, name: TRANSLATIONS.cover_doublepage_0, callback: function () { + $this.coverPage(0, true); + }, + }, + }); + } + } + if (hasSelection) { + res.items = $.extend(res.items, { + 'sep0': '---------', "delete": { + isHtmlName: true, + name: (multiple ? TRANSLATIONS.delete_selection : TRANSLATIONS.delete_link) + ' Del', + callback: function () { + $this.deleteSelection(); + } + }, + }); + } + return res; + }, + }); + + setInterval(function () { + $this.checkLastSelectedLink(); + }, 250); + }, + + key: function (shortcut, scope) { + var $this = this; + key(shortcut, function (event, handler) { + if ($this.allowsKeyboardShortcut(shortcut)) { + var res = scope(event, handler); + return res; + } + }); + }, + + copy: function () { + this.copySelectionToClipboard(false); + }, + + cut: function () { + this.copySelectionToClipboard(true); + }, + + paste: function (frommouse) { + let $this = this; + this.linkeditor.clipboard.get(function (content) { + $this._paste(content, frommouse); + }) + }, + + _paste: function (content, frommouse) { + var clipboard = $('
').html(content); + + var linksInClipboard = clipboard.find(".link"); + if (!linksInClipboard.length) { + return; + } + var $this = this; + + let offset = {x: 0, y: 0}; + if (frommouse === 'left') { + offset.x = -this.linkeditor.pw; + } else if (frommouse === 'right') { + offset.x = this.linkeditor.pw; + } else if (frommouse !== undefined) { + // Base mouse position should be the start of context menu (and not the mouse position when we click on the Paste in place item menu) + offset = this.linkeditor.globalToFluidbook(frommouse.x, frommouse.y, this.linkeditor.single); + + var top = 1000000; + var left = 1000000; + // Get the coordinates of the top left corner of the links in clipboard + $(linksInClipboard).each(function () { + top = Math.min(parseFloat($(this).attr('fb-top')), top); + left = Math.min(parseFloat($(this).attr('fb-left')), left); + }); + // The links will be pasted in order the links will be disposed as initially but starting from the mouse current position + offset.x -= left; + offset.y -= top; + } + + this.deselectAllLinks(); + $(linksInClipboard).each(function () { + let data = $this._duplicateLink($(this), false); + + data.top += offset.y; + data.left += offset.x; + + $this.selectLink($this.addLink(data, false)); + }); + + this.linkeditor.hasChanged(); + this.updatePolygonLinks(); + this.updateLayers(); + }, + + hasUIDLink: function (uid) { + return LINKS[uid] !== undefined && LINKS[uid] !== null; + }, + + copySelectionToClipboard: function (cut) { + var selection = this.getCurrentSelection(); + if (selection.length === 0) { + return; + } + var $this = this; + + let nb = 0; + + var clipboardContent = $('
'); + $.each(selection, function () { + let item = $(this); + let clone = $(item).clone(); + if (cut) { + $this.deleteLink(item, false); + } + clipboardContent.append(clone); + nb++; + }); + + this.linkeditor.clipboard.set(clipboardContent.html()); + + if (cut) { + this.linkeditor.form.emptyForm(); + this.linkeditor.hasChanged(); + this.updateLayers(); + } + + var msg = cut ? TRANSLATIONS.n_links_cut : TRANSLATIONS.n_links_copied; + msg = msg.replace('%nb%', nb, msg) + this.linkeditor.notification(msg); + }, + + + fixDriftedLinks: function () { + var $this = this; + var callback = function () { + $this._fixDriftedLinks(); + } + + //Restore links from 2022-12-07 13:37:15 + this.linkeditor.save.saveIfUnsavedChanges(TRANSLATIONS.before_fix_drifted, false, callback); + }, + + _fixDriftedLinks: function () { + $.ajax({ + url: '/fluidbook-publication/' + FLUIDBOOK_DATA.id + '/edit/links/fixdriftedlinks', + success: function (data) { + window.location.reload(); + }, + }); + }, + + allowsKeyboardShortcut: function (shortcut) { + if (shortcut === 'pageup' || shortcut === 'pagedown' || shortcut === 'enter') { + return true; + } + return !this.linkeditor.utils.isfocusOnFormItem(); + }, + + getLinkById: function (uid) { + return $('.link[fb-uid="' + uid + '"]'); + }, + + + openImageLink: function () { + let selection = $(".link.selected").eq(0); + if (undefined === CAN_CONTAIN_LINKS[parseInt($(selection).attr('fb-type'))] || selection.length !== 1) { + this.linkeditor.notification(TRANSLATIONS.error_open_image_link, 'warning'); + return; + } + this.linkeditor.changePage('link_uid_' + selection.attr('fb-uid')); + }, + + selectLinkAndSelectToField: function (link) { + if ($(link).length === 0) { + return; + } + this.deselectAllLinks(); + this.selectLink($(link)); + this.updateSelection(); + this.linkeditor.form.focusAndSelectDestinationField(); + }, + + selectFirstLink: function () { + this.selectLinkAndSelectToField($('#linkeditor-links .link:eq(0)')); + }, + + selectLastLink: function () { + this.selectLinkAndSelectToField($('#linkeditor-links .link:last')); + }, + + selectPreviousLink: function () { + if (this.getCurrentSelection().length === 0) { + return this.selectLastLink(); + } + return this.selectLinkAndSelectToField(this._getLinkByIndexOffset(-1)); + }, + + selectNextLink: function () { + if (this.getCurrentSelection().length === 0) { + return this.selectFirstLink(); + } + this.selectLinkAndSelectToField(this._getLinkByIndexOffset(1)); + }, + + _getLinkByIndexOffset(way) { + let allLinks = this.getCurrentLinksOnPage(); + let index = this.getFirstLinkInSelection().index(); + let nb = allLinks.length; + let n = (nb + index + way) % nb; + return allLinks.eq(n); + }, + + cleanPendingCreateLink() { + $('.pendingCreate').remove(); + }, + + createLinkDrag: function () { + var link = this.duplicateLinkDrag({width: 0, height: 0, polygon: null}); + $(link).addClass('pendingCreate').addClass('new'); + this.deselectAllLinks(); + this.selectLink($(link)); + this.startResizeLink('se'); + this.updateLayers(); + }, + + startResizeLink: function (corner) { + this.resizeLinkPos = {x: this.linkeditor.mx, y: this.linkeditor.my, corner: corner}; + this.setDragOrigValues(); + }, + + stopResizeLink: function () { + if (this.resizeLinkPos === null) { + return; + } + var $this = this; + this.moveResizeLink(); + $('.pendingCreate').each(function () { + $this.deleteLink($(this), true); + }); + this.linkeditor.form.updateLinkForm(); + this.resizeLinkPos = null; + this.linkeditor.hasChanged(); + }, + + moveResizeLink: function () { + if (this.resizeLinkPos === null) { + return; + } + let $this = this; + let f = 1 / (this.linkeditor.fs * this.linkeditor.zoom.zoom); + let dx = (this.linkeditor.mx - this.resizeLinkPos.x) * f; + let dy = (this.linkeditor.my - this.resizeLinkPos.y) * f; + + let top = 0, left = 0, width = 0, height = 0; + + if (['n', 'ne', 'nw'].indexOf(this.resizeLinkPos.corner) >= 0) { + top = dy; + height = -dy; + } else if (['s', 'se', 'sw'].indexOf(this.resizeLinkPos.corner) >= 0) { + height = dy; + } + if (['nw', 'w', 'sw'].indexOf(this.resizeLinkPos.corner) >= 0) { + left = dx; + width = -dx; + } else if (['ne', 'e', 'se'].indexOf(this.resizeLinkPos.corner) >= 0) { + width = dx; + } + + let magnet = !key.ctrl; + + $(".link.selected").each(function () { + let newWidth = $(this).data('drag-orig-width') + width; + let newHeight = $(this).data('drag-orig-height') + height; + + if (key.shift) { + magnet = false; + // Keep ratio + let ratio = $(this).data('drag-orig-width') / $(this).data('drag-orig-height'); + let xscale = newWidth / $(this).data('drag-orig-width'); + let yscale = newHeight / $(this).data('drag-orig-height'); + if (['ne', 'nw', 'se', 'sw'].indexOf($this.resizeLinkPos.corner) >= 0) { + if (xscale < yscale) { + newHeight = newWidth / ratio; + } else { + newWidth = newHeight * ratio; + } + } else if (['e', 'w'].indexOf($this.resizeLinkPos.corner) >= 0) { + newHeight = newWidth / ratio; + } else if (['n', 's'].indexOf($this.resizeLinkPos.corner) >= 0) { + newWidth = newHeight * ratio; + } + + width = newWidth - $(this).data('drag-orig-width'); + height = newHeight - $(this).data('drag-orig-height'); + + if (['ne', 'e', 'se'].indexOf($this.resizeLinkPos.corner) >= 0) { + left = 0; + } else if (['nw', 'w', 'sw'].indexOf($this.resizeLinkPos.corner) >= 0) { + left = width; + } + if (['ne', 'n', 'nw'].indexOf($this.resizeLinkPos.corner) >= 0) { + top = height; + } else if (['se', 's', 'sw'].indexOf($this.resizeLinkPos.corner) >= 0) { + top = 0; + } + } + + let newLeft = $(this).data('drag-orig-left') + left; + let newTop = $(this).data('drag-orig-top') + top; + + if (magnet) { + let diff = 0; + if (left !== 0) { + let magnetLeft = $this.linkeditor.utils.magnetize(newLeft, $this.magnetValuesX); + diff = newLeft - magnetLeft; + newLeft = magnetLeft; + newWidth += diff; + } + if (top !== 0) { + let magnetTop = $this.linkeditor.utils.magnetize(newTop, $this.magnetValuesY) + diff = newTop - magnetTop; + newTop = magnetTop; + newHeight += diff; + } + if (width !== 0) { + let magnetLeft = $this.linkeditor.utils.magnetize(newLeft, $this.magnetValuesX, newWidth, true); + diff = newLeft - magnetLeft; + newWidth -= diff; + } + if (height !== 0) { + let magnetTop = $this.linkeditor.utils.magnetize(newTop, $this.magnetValuesY, newHeight, true); + diff = newTop - magnetTop; + newHeight -= diff; + } + } + + if (newWidth < 0) { + newWidth *= -1; + newLeft -= newWidth; + } + if (newHeight < 0) { + newHeight *= -1; + newTop -= newHeight; + } + + $(this).attr('fb-left', newLeft) + .attr('fb-top', newTop) + .attr('fb-width', newWidth) + .attr('fb-height', newHeight) + .attr('fb-update', '1'); + + if ($(this).hasClass('pendingCreate') && newWidth > 2 && newHeight > 2) { + $(this).removeClass('pendingCreate'); + } + + $this.updateLinkData($(this).attr('fb-origuid'), { + top: newTop, left: newLeft, width: newWidth, height: newHeight + }); + }); + this.linkeditor.updateFBElements(false); + this.linkeditor.save.hasChanged(); + }, + + + startDragLink: function () { + this.dragLinkPos = {x: this.linkeditor.mx, y: this.linkeditor.my}; + this.setDragOrigValues(); + }, + + setDragOrigValues: function () { + $(".link.selected").each(function () { + $(this).data('drag-orig-left', parseFloat($(this).attr('fb-left'))); + $(this).data('drag-orig-top', parseFloat($(this).attr('fb-top'))); + $(this).data('drag-orig-width', parseFloat($(this).attr('fb-width'))); + $(this).data('drag-orig-height', parseFloat($(this).attr('fb-height'))); + }); + }, + + stopDragLink: function () { + if (this.dragLinkPos === null) { + return; + } + this.moveDragLink(); + this.dragLinkPos = null; + this.linkeditor.hasChanged(); + }, + + moveDragLink: function () { + if (this.updateRectSelection()) { + return; + } + if (this.dragLinkPos === null) { + return; + } + + let magnet = !key.ctrl; + let $this = this; + let f = 1 / (this.linkeditor.fs * this.linkeditor.zoom.zoom); + let dx = (this.linkeditor.mx - this.dragLinkPos.x) * f; + let dy = (this.linkeditor.my - this.dragLinkPos.y) * f; + + var rect = {left: 10000000, top: 10000000, right: 0, bottom: 0}; + + if (magnet) { + $(".link.selected").each(function () { + let left = $(this).data('drag-orig-left') + dx; + let top = $(this).data('drag-orig-top') + dy; + let right = left + $(this).data('drag-orig-width'); + let bottom = top + $(this).data('drag-orig-height'); + + rect.left = Math.min(left, rect.left); + rect.right = Math.max(right, rect.right); + rect.top = Math.min(top, rect.top); + rect.bottom = Math.max(bottom, rect.bottom); + }); + + let rx = $this.linkeditor.utils.magnetize(rect.left, $this.magnetValuesX, rect.right - rect.left) + let ry = $this.linkeditor.utils.magnetize(rect.top, $this.magnetValuesY, rect.bottom - rect.top); + + dx -= rect.left - rx; + dy -= rect.top - ry; + } + + + $(".link.selected").each(function () { + let left = $(this).data('drag-orig-left') + dx; + let top = $(this).data('drag-orig-top') + dy; + + $(this) + .attr('fb-left', left) + .attr('fb-top', top) + .attr('fb-update', '1'); + + $this.updateLinkData($(this).attr('fb-origuid'), { + top: top, left: left, + }); + }); + this.linkeditor.updateFBElements(false); + this.linkeditor.save.hasChanged(); + }, + + selectLink: function (l) { + if ($(l).find('.corners').length === 0) { + $(l).append('
') + } + $(l).addClass('selected'); + + this.linkeditor.form.updateLinkForm(); + if ($(l).is(':visible') && !$(l).is('.pendingCreate') && parseFloat($(l).attr('fb-width')) > 2 && parseFloat($(l).attr('fb-height')) > 2) { + this.setLastSelectedLink(l); + } + this.updateSelection(); + if ($(l).hasClass('new')) { + this.linkeditor.form.focusAndSelectDestinationField(); + } + }, + + setLastSelectedLink: function (l) { + this.lastSelectedLink = l; + this.checkLastSelectedLink(); + }, + + checkLastSelectedLink: function () { + if (this.lastSelectedLink === null || $(this.lastSelectedLink).length === 0) { + return; + } + this.lastSelectedLinkData = this.getLinkData(this.lastSelectedLink); + }, + + roundPos: function (v) { + + }, + + getLinkData: function (l) { + var res = {}; + var attributes = $(l).attr(); + var skip = ['fb-ref', 'fb-update']; + for (var key in attributes) { + if (key.indexOf('fb-') !== 0 || skip.indexOf(key) >= 0) { + continue; + } + res[key.substring(3)] = attributes[key]; + } + return res; + }, + + deselectAllLinks: function () { + this.linkeditor.form.saveFormDataInLink(); + this.linkeditor.form.updateLinkForm(); + $(".link.selected").removeClass('selected').removeClass('new'); + this.updateSelection(); + }, + + deselectLink: function (link) { + $(link).removeClass('selected').removeClass('new'); + this.updateSelection(); + }, + + offsetSelectedLinks: function (dim, value) { + var $this = this; + $('.link.selected').each(function () { + let v = parseFloat($(this).attr('fb-' + dim)); + let newValue = v + value; + let data = {}; + data[dim] = newValue; + $(this).attr('fb-' + dim, newValue).attr('fb-update', '1'); + $this.updateLinkData($(this).attr('fb-origuid'), data); + }); + this.linkeditor.updateFBElements(); + this.linkeditor.form.updateFormData(); + }, + + getLinksOfPage: function (p) { + let pages = []; + if (!this.linkeditor.single) { + if (p % 2 === 1) { + p--; + } + pages.push(p); + pages.push(p + 1); + } else { + pages.push(p); + } + let res = {}; + $.each(LINKS, function (uid, link) { + if (pages.indexOf(parseInt(link.page)) >= 0) { + res[uid] = link; + } + }); + return res; + }, + + loadLinks: function (page, side) { + let $this = this; + this.normalizeLinksPage(); + $.each(LINKS, function (uid, link) { + if ($('#linkeditor-links [fb-uid="' + uid + '"]').length > 0) { + return; + } + if (link.page != page) { + return; + } + $this.addLink(link, false); + }); + this.updatePolygonLinks(); + this.updateLayers(); + this.locks.update(); + this.linkeditor.undo.initState(); + }, + + normalizeLinksPage() { + let pw = this.linkeditor.pw; + let single = this.linkeditor.single; + $.each(LINKS, function (uid, link) { + link.left = parseFloat(link.left) + if (!single) { + if (link.page % 2 === 0 && link.left > pw) { + link.page++; + link.left -= pw; + } + if (link.page % 2 === 1) { + link.page--; + link.left += pw; + } + } + LINKS[uid] = link; + }); + }, + + + addLink: function (link, triggerChange) { + if (triggerChange === undefined) { + triggerChange = true; + } + let change = false; + if (link.uid === undefined) { + link.uid = this.linkeditor.utils.generateUID(); + change = true; + } else if (!this.hasUIDLink(link.uid)) { + change = true; + } + if (change) { + LINKS[link.uid] = link; + } + + link.origuid = link.uid; + + let $this = this; + + let attrs = {}; + $.each(link, function (k, v) { + attrs['fb-' + k] = $this.linkeditor.form.normalizeFormValue(k, v); + }); + attrs['fb-ref'] = "editor"; + attrs['fb-update'] = "1"; + + + let e = $(''); + $(e).attr(attrs); + if (this.dropTypes.indexOf(parseInt(link.type)) >= 0) { + $(e).addClass('dropfile'); + } + $("#linkeditor-links").append(e); + if (triggerChange && change) { + this.linkeditor.rulers.updateMagnetValues(); + this.linkeditor.hasChanged(); + } + return e; + }, + + updateMagnetValues: function () { + let $this = this; + this.magnetValuesX = []; + this.magnetValuesY = []; + $.each(this.linkeditor.rulers.getRulersOfPage(), function (uid, ruler) { + if (ruler.type === 'y') { + $this.magnetValuesY.push(ruler.pos); + } else { + $this.magnetValuesX.push(ruler.pos); + } + }); + }, + + _duplicateLink: function (link, pos) { + var data; + if (link === undefined) { + data = this.lastSelectedLinkData; + } else { + data = this.getLinkData(link); + } + data.page = this.linkeditor.currentPage; + if (pos === undefined) { + pos = this.linkeditor.globalToFluidbook(this.linkeditor.mx, this.linkeditor.my, this.linkeditor.single); + } + if (pos !== false) { + data.left = pos.x; + data.top = pos.y; + } else { + data.left = parseFloat(data.left); + data.top = parseFloat(data.top); + } + if (this.hasUIDLink(data.uid)) { + delete data.uid; + } + return data; + }, + + duplicateLinkClick: function () { + let link = this.addLink(this._duplicateLink(), true); + $(link).addClass('new'); + + this.deselectAllLinks(); + this.selectLink($(link)); + this.updatePolygonLinks(); + this.linkeditor.form.updateFormData(); + + return $(link); + }, + + duplicateLinkDrag(overwriteData) { + var data = this._duplicateLink(); + if (overwriteData !== undefined) { + $.extend(data, overwriteData); + } + return $(this.addLink(data, false)); + }, + + updateLayers: function () { + this.updateDepths(); + this.linkeditor.layers.update(); + }, + + deleteSelection: function () { + var $this = this; + this.linkeditor.form.emptyForm(); + $(".link.selected").each(function () { + $this.deleteLink(this, false); + }); + this.linkeditor.hasChanged(); + }, + + getCurrentSelection: function () { + return $('#linkeditor .link.selected:not(.pendingCreate)'); + }, + + getFirstLinkInSelection: function () { + return this.getCurrentSelection().eq(0); + }, + + getCurrentLinksOnPage() { + return $('.link:not(.pendingCreate)'); + }, + + deleteLink: function (link, triggerChange) { + if (triggerChange === undefined) { + triggerChange = true; + } + delete LINKS[$(link).attr('fb-origuid')]; + $(link).remove(); + if (triggerChange === true) { + this.linkeditor.hasChanged(); + } + this.updateLayers(); + this.updateSelection(); + }, + + selectAll: function () { + let $this = this; + $('.link').each(function () { + $this.selectLink($(this)); + }); + this.updateSelection(); + }, + + selectAllExceptLocked: function () { + let $this = this; + $('.link:not([data-locked="1"])').each(function () { + $this.selectLink($(this)); + }); + this.updateSelection(); + }, + + updateSelection: function () { + $("#linkeditor").attr('data-selection-count', this.getCurrentSelection().length); + this.linkeditor.layers.updateSelection(); + }, + + updateDepths: function () { + var $this = this; + $("#linkeditor-links .link").each(function () { + let calcDepth = parseInt($(this).attr('fb-zindex')); + if (isNaN(calcDepth) || calcDepth === -1) { + calcDepth = $this.findDefaultLinkDepth($(this)); + } + let linkWidth = parseFloat($(this).attr('fb-width')); + let linkHeight = parseFloat($(this).attr('fb-height')); + let zindex = ((calcDepth + 1) * 10000) - Math.min(9999, Math.max(1, Math.round(9999 * ((linkWidth * linkHeight) / $this.linkeditor.bookSurface)))); + + if (isNaN(zindex) || isNaN(calcDepth)) { + console.warn('error defining depth of link ' + $(this).attr('fb-uid'), calcDepth, $this.findDefaultLinkDepth($(this)), linkWidth, linkHeight, $this.linkeditor.bookSurface); + } + + $(this).attr('fb-calc-depth', calcDepth); + $(this).attr('fb-calc-zindex', zindex); + $(this).css('z-index', zindex); + }); + }, + + findDefaultLinkDepth: function (link) { + var conf = DEPTH.configs[$(link).attr('fb-type')]; + var key = $(link).attr('fb-type'); + var settings = []; + $.each(conf, function (k, v) { + let val = $(link).attr('fb-' + k); + if (k === 'alternative' || k === 'to') { + if (key == 6) { + let e = val.split('.'); + let ext = e.pop().toLowerCase(); + val = (['png', 'jpg', 'jpeg', 'gif', 'webp', 'avif', 'svg'].indexOf(ext) >= 0) ? 'file.jpg' : 'file.zip'; + } + } + settings.push(k + '|' + val); + }); + if (settings.length > 0) { + key += '/' + settings.join(','); + } + + let res = DEPTH.depths[key]; + if (res === undefined) { + console.log('undefined depth key ', key); + } + return res; + }, + + clear: function () { + $("#linkeditor-links").html(''); + }, + + alignSelection: function (align) { + let d = this.getMinMaxSelection(align); + if (align === 'left' || align === 'top') { + this.getCurrentSelection().each(function () { + $(this).attr('fb-' + d.side, d.min); + }); + } else if (align === 'right' || align === 'bottom') { + this.getCurrentSelection().each(function () { + $(this).attr('fb-' + d.side, d.max - parseFloat($(this).attr('fb-' + d.length))); + }); + } else if (align === 'center' || align === 'middle') { + let center = d.min + ((d.max - d.min) / 2); + this.getCurrentSelection().each(function () { + $(this).attr('fb-' + d.side, center - parseFloat($(this).attr('fb-' + d.length)) / 2); + }); + } + this.updateSelectionData([d.side]); + this.linkeditor.hasChanged(); + }, + + dimensionSelection: function (dimension, skipChanged) { + if (dimension === 'both') { + this.dimensionSelection('width', true); + this.dimensionSelection('height', true); + } else { + let d = this.getMinMaxSelection(dimension); + this.getCurrentSelection().each(function () { + $(this).attr('fb-' + dimension, d.maxl); + }); + } + if (skipChanged === undefined || skipChanged !== true) { + this.updateSelectionData(['width', 'height']); + this.linkeditor.hasChanged(); + } + }, + + updateLinkData: function (id, data, updateHTML) { + if (LINKS[id] === undefined) { + console.warn('Link ' + id + ' not found'); + return; + } + + let htmlLink = updateHTML ? $('.link[fb-uid="' + id + '"]') : false; + + $.each(data, function (k, v) { + LINKS[id][k] = v; + if (htmlLink) { + $(htmlLink).attr('fb-' + k, v); + } + }); + this.linkeditor.rulers.updateMagnetValues(); + this.updateLayers(); + this.updatePolygonLinks(false); + }, + + updateSelectionData: function (props) { + this.getCurrentSelection().each(function () { + let uid = $(this).attr('fb-uid'); + for (let i = 0; i < props.length; i++) { + let prop = props[i]; + LINKS[uid][prop] = $(this).attr('fb-' + prop); + } + }); + this.linkeditor.rulers.updateMagnetValues(); + this.updateLayers(); + }, + + distributeSelection: function (axis) { + let d = this.getMinMaxSelection(axis); + let totalLength = d.max - d.min; + let lengthSum = 0; + let links = []; + this.getCurrentSelection().each(function () { + let l = parseFloat($(this).attr('fb-' + d.length)); + links.push({pos: $(this).attr('fb-' + d.side), length: l, link: $(this)}); + lengthSum += l; + }); + let space = (totalLength - lengthSum) / (links.length - 1); + links.sort(function (a, b) { + return a.pos - b.pos; + }); + var s = d.min; + $.each(links, function (k, link) { + $(link.link).attr('fb-' + d.side, s); + s += link.length + space; + }); + this.updateSelectionData([d.side]); + this.linkeditor.hasChanged(); + }, + + getMinMaxSelection: function (axis) { + if (axis === 'left' || axis === 'right' || axis === 'center' || axis === 'width') { + axis = 'x'; + } else if (axis === 'top' || axis === 'bottom' || axis === 'middle') { + axis = 'y;' + } + + var b = axis === 'x' ? 'left' : 'top'; + var l = axis === 'x' ? 'width' : 'height'; + var max = -100000000; + var maxl = max; + var min = 100000000; + var minl = minl; + this.getCurrentSelection().each(function () { + let vb = parseFloat($(this).attr('fb-' + b)); + let vl = parseFloat($(this).attr('fb-' + l)); + min = Math.min(min, vb); + minl = Math.min(minl, vl); + max = Math.max(max, vb + vl); + maxl = Math.max(maxl, vl); + }); + return {min: min, max: max, minl: minl, maxl: maxl, side: b, length: l}; + }, + + coverPage(margin, double) { + if (double === undefined) { + double = false; + } + if (margin === undefined) { + margin = 0; + } + var link = this.getCurrentSelection().eq(0); + var rect = { + x: -margin, y: -margin, width: this.linkeditor.fw + margin * 2, height: this.linkeditor.ph + margin * 2, + }; + if (!this.linkeditor.single && !double) { + if (parseFloat($(link).attr('fb-left')) > this.linkeditor.pw) { + rect.x = this.linkeditor.pw - margin; + } + rect.width = this.linkeditor.pw + margin * 2; + } + link.attr('fb-left', rect.x).attr('fb-top', rect.y).attr('fb-width', rect.width).attr('fb-height', rect.height); + this.updateSelectionData(['left', 'top', 'width', 'height']); + this.linkeditor.hasChanged(); + }, + + getCurrentState: function () { + let data = []; + let $this = this; + $(".link:not(.pendingCreate):not([fb-width=0]):not([fb-height=0])").each(function () { + let l = {}; + $.each(LINKS[$(this).attr('fb-uid')], function (k, v) { + l[k] = $this.linkeditor.form.normalizeFormValue(k, v); + }); + data.push(l); + }); + data.sort(function (a, b) { + return a.toString().localeCompare(b.toString()); + }); + return JSON.stringify(data); + }, + + setCurrentState: function (state) { + let links = JSON.parse(state); + this.clear(); + var $this = this; + var existingUIDs = []; + // Update existing and add new links + $.each(links, function (k, link) { + $this.addLink(link, false); + LINKS[link.uid] = link; + existingUIDs.push(link.uid); + }); + // Search for missing links and delete them + var pages = this.linkeditor.getCurrentPages(); + var currentPages = []; + $.each(pages, function (k, v) { + currentPages.push(v.toString()); + }) + $.each(LINKS, function (uid, link) { + if (currentPages.indexOf(link.page.toString()) >= 0) { + if (existingUIDs.indexOf(link.uid) === -1) { + console.log("missing link", LINKS[uid], " deleting"); + delete LINKS[uid]; + } + } + }); + + this.linkeditor.hasChanged(false); + }, + + + startRectSelection: function () { + $('.link.selected').addClass('selectedBeforeRect'); + this.rectSelection = this.linkeditor.globalToCanvas(this.linkeditor.mx, this.linkeditor.my); + }, + + updateRectSelection: function () { + if (this.rectSelection === null) { + return false; + } + + var $this = this; + let pos = this.linkeditor.globalToCanvas(this.linkeditor.mx, this.linkeditor.my); + + var css = {}; + css.width = pos.x - this.rectSelection.x; + css.height = pos.y - this.rectSelection.y; + css.left = this.rectSelection.x; + css.top = this.rectSelection.y; + if (css.width < 0) { + css.left = pos.x; + css.width *= -1; + } + if (css.height < 0) { + css.top = pos.y; + css.height *= -1; + } + + $("#linkeditor-selectlink-rect").show(); + $("#linkeditor-selectlink-rect").css(css); + + let selectRect = $("#linkeditor-selectlink-rect").get(0).getBoundingClientRect(); + $(".link:not(.selectedBeforeRect):not([data-locked=\"1\"])").each(function () { + if ($this.linkeditor.utils.intersectRect($(this).get(0).getBoundingClientRect(), selectRect)) { + $this.selectLink(this); + } else { + $this.deselectLink(this); + } + }); + + return true; + }, + + endRectSelection: function () { + if (this.rectSelection === null) { + return false; + } + $(".selectedBeforeRect").removeClass('selectedBeforeRect'); + $("#linkeditor-selectlink-rect").hide(); + this.rectSelection = null; + return true; + }, + + + importFromPDF: function () { + var $this = this; + var callback = function () { + $.ajax({ + url: '/fluidbook-publication/' + FLUIDBOOK_DATA.id + '/edit/links/import/pdf', + success: function (data) { + window.location.reload(); + }, + }); + }; + + this.linkeditor.save.saveIfUnsavedChanges(TRANSLATIONS.before_import_links_from_pdf, false, callback); + }, + + mouseUp: function () { + this.endRectSelection(); + this.stopDragLink(); + this.stopResizeLink(); + this.stopMovePolygonPoint(); + this.cleanPendingCreateLink(); + }, + + mouseDown: function () { + if ($('#linkeditor-main').hasClass('polygon')) { + this.beginPolygonLine(); + } else { + this.createLinkDrag(); + } + }, + + beginPolygonLine: function () { + var link; + var pos = this.linkeditor.globalToFluidbook(this.linkeditor.mx, this.linkeditor.my, this.linkeditor.single); + let polygon; + if ($('.pendingPolygonCreate').length === 0) { + link = this.duplicateLinkDrag({width: 0, height: 0, left: pos.x, top: pos.y}); + $(link).addClass('pendingPolygonCreate'); + polygon = [pos]; + } else { + link = $('.pendingPolygonCreate'); + polygon = this.getOffsetPolygon(link); + polygon.push(pos); + } + + this.selectLink($(link)); + this.updatePolygonLink(link, polygon); + this.linkeditor.hasChanged(); + }, + + closePolygonShape: function () { + $('.pendingPolygonCreate').removeClass('pendingPolygonCreate'); + }, + + getOffsetPolygon: function (link) { + let left = parseFloat($(link).attr('fb-left')); + let top = parseFloat($(link).attr('fb-top')); + try { + let points = JSON.parse($(link).attr('fb-polygon')); + let offset = []; + $.each(points, function (k, v) { + offset.push({x: parseFloat(v.x) + left, y: parseFloat(v.y) + top}); + }); + return offset; + } catch (e) { + return null; + } + }, + + updatePolygonLinks: function (updateData) { + if (updateData === undefined) { + updateData = true; + } + let $this = this; + $('.link[fb-polygon]').each(function () { + let polygon = $this.getOffsetPolygon($(this)); + if (polygon !== null) { + $this.updatePolygonLink($(this), polygon, updateData); + } else { + $(this).find('svg,.corners').remove(); + $(this).attr('fb-polygon', null); + } + }); + }, + + updatePolygonLink: function (link, polygon, updateData) { + if (updateData === undefined) { + updateData = true; + } + let $this = this; + + let minx = Number.MAX_VALUE; + let maxx = Number.MIN_VALUE; + let miny = Number.MAX_VALUE; + let maxy = Number.MIN_VALUE; + + $.each(polygon, function (k, pos) { + minx = Math.min(minx, pos.x); + miny = Math.min(miny, pos.y); + maxx = Math.max(maxx, pos.x); + maxy = Math.max(maxy, pos.y); + }); + + let normalizedPolygon = []; + + let w = maxx - minx; + let h = maxy - miny; + + $(link).attr('fb-width', w); + $(link).attr('fb-height', h); + $(link).attr('fb-left', minx); + $(link).attr('fb-top', miny); + + let svg = '
'); + let clippath = []; + + $.each(polygon, function (k, pos) { + let point = { + x: $this.linkeditor.utils.roundDimension(pos.x - minx), + y: $this.linkeditor.utils.roundDimension(pos.y - miny) + }; + let cx = (point.x / w) * 100; + let cy = (point.y / h) * 100; + svg += point.x + ',' + point.y + ' '; + normalizedPolygon.push(point); + $(corners).append('
'); + + let cs = 1.05; + let ccx = ((cx / 100) * 100 * cs) - ((cs * 100) - 100) / 2; + let ccy = ((cy / 100) * 100 * cs) - ((cs * 100) - 100) / 2; + clippath.push([ccx, ccy]); + }); + + + svg += '" fill="currentColor" stroke="currentColor" stroke-width="1.25" fill-opacity="0.25" vector-effect="non-scaling-stroke">'; + $(link).css('clip-path', 'polygon(' + this.getConvexClipPath(clippath) + ')'); + $(link).html(svg); + $(link).append(corners); + let jsonPolygon = JSON.stringify(normalizedPolygon); + $(link).attr('fb-polygon', jsonPolygon); + if (updateData) { + this.updateLinkData($(link).attr('fb-uid'), { + left: minx, top: miny, width: w, height: h, polygon: jsonPolygon + }); + } + }, + + + getConvexClipPath: function (points) { + const grahamscan = new GrahamScan(); + grahamscan.setPoints(points); + let hull = grahamscan.getHull(); + let res = []; + $.each(hull, function (k, p) { + res.push(p[0] + '% ' + p[1] + '%'); + }); + return res.join(','); + }, + + startMovePolygonPoint: function (idx) { + let link = this.getFirstLinkInSelection(); + let polygon = this.getOffsetPolygon(link); + this.movePolygonPointPos = { + x: this.linkeditor.mx, y: this.linkeditor.my, ox: polygon[idx].x, oy: polygon[idx].y, index: idx + }; + this.setDragOrigValues(); + }, + + stopMovePolygonPoint: function () { + if (this.movePolygonPointPos === null) { + return; + } + var $this = this; + this.linkeditor.form.updateLinkForm(); + this.movePolygonPointPos = null; + this.updatePolygonLinks(); + this.linkeditor.hasChanged(); + }, + + movePolygonPoint: function () { + if (this.movePolygonPointPos === null) { + return; + } + let $this = this; + let f = 1 / (this.linkeditor.fs * this.linkeditor.zoom.zoom); + let dx = (this.linkeditor.mx - this.movePolygonPointPos.x) * f; + let dy = (this.linkeditor.my - this.movePolygonPointPos.y) * f; + + + let link = this.getFirstLinkInSelection(); + let polygon = this.getOffsetPolygon(link); + polygon[this.movePolygonPointPos.index].x = this.movePolygonPointPos.ox + dx; + polygon[this.movePolygonPointPos.index].y = this.movePolygonPointPos.oy + dy; + + this.updatePolygonLink(link, polygon); + $(link).attr('fb-update', '1'); + + this.linkeditor.updateFBElements(false); + this.linkeditor.save.hasChanged(); + }, +}; + +export default LinkeditorLinks; diff --git a/resources/linkeditor-stable/js/linkeditor.links.lock.js b/resources/linkeditor-stable/js/linkeditor.links.lock.js new file mode 100644 index 000000000..6b43396df --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.links.lock.js @@ -0,0 +1,60 @@ +function LinkeditorLinksLock(linkeditor) { + this.linkeditor = linkeditor; + this.locked = []; +} + +LinkeditorLinksLock.prototype = { + isLocked: function (uid) { + return this.locked.indexOf(uid) >= 0; + }, + + toggleLock: function (uid) { + if (this.isLocked(uid)) { + this.unlock([uid]); + } else { + this.lock([uid]); + } + }, + + lock: function (uid) { + for (let i in uid) { + this.locked.push(uid[i]); + } + this.locked = this.linkeditor.utils.array_unique(this.locked); + this.update(); + }, + + unlock: function (uid) { + for (let i in uid) { + let u = this.locked.splice(this.locked.indexOf(uid[i]), 1); + } + this.locked = this.linkeditor.utils.array_unique(this.locked); + this.update(); + }, + + lockSelection: function () { + let uid = []; + $('.link.selected').each(function () { + uid.push($(this).attr('fb-uid')); + }); + this.lock(uid); + }, + + update: function () { + // Unlock layers + $('#linkeditor-panel-layers [data-locked="1"]').attr('data-locked', '0'); + $('#linkeditor-links .link[data-locked="1"]').attr('data-locked', '0'); + + for (let i in this.locked) { + let uid = this.locked[i]; + // Lock layer + $('input[name="' + uid + '"]').closest('[data-locked]').attr('data-locked', '1'); + // Unselect link + let link = this.linkeditor.links.getLinkById(uid); + this.linkeditor.links.deselectLink(link); + link.attr('data-locked', '1'); + } + }, +} + +module.exports = LinkeditorLinksLock; diff --git a/resources/linkeditor-stable/js/linkeditor.loader.js b/resources/linkeditor-stable/js/linkeditor.loader.js new file mode 100644 index 000000000..09fdaa928 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.loader.js @@ -0,0 +1,96 @@ +function LinkeditorLoader(linkeditor) { + this.linkeditor = linkeditor; + this.preloadTimeouts = []; + this.init(); +} + +LinkeditorLoader.prototype = { + + init: function () { + this.pagesSource = 'pages'; + this.rasterizePages = this.linkeditor.utils.splitPages(FLUIDBOOK_DATA.settings.rasterizePages); + this.vectorPages = this.linkeditor.utils.splitPages(FLUIDBOOK_DATA.settings.vectorPages); + this.noCache = '?t=' + (new Date(FLUIDBOOK_DATA.composition_updated_at)).getTime(); + }, + + loadPage: function (p, side) { + var container = $("#linkeditor-page-" + side); + $(container).attr('data-page', p); + + if (p === 0 || p > FLUIDBOOK_DATA.settings.pages) { + $(container).html(''); + } else { + this._loadPage(p, container); + } + + this.linkeditor.resize.resizePages(); + + this.linkeditor.links.loadLinks(p, side); + this.linkeditor.rulers.loadRulers(p, side); + }, + + togglePagesSource: function () { + var $this = this; + this.pagesSource = this.pagesSource === 'pages' ? 'thumbnails' : 'pages'; + this.clearPreloads(); + $(".linkeditor-page").each(function () { + let p = parseInt($(this).attr('data-page')); + if (isNaN(p) || p === 0 || p > FLUIDBOOK_DATA.settings.pages) { + $(this).html(''); + } else { + $this._loadPage(p, $(this)); + } + }); + setTimeout(function () { + $this.preloadPages(); + }, 2000); + }, + + clearPreloads: function () { + $.each(this.preloadTimeouts, function (k, timeout) { + clearTimeout(timeout); + }); + $("#linkeditor-preload").html(''); + }, + + preloadPages: function () { + let j = 1; + var $this = this; + for (let i = Math.max(1, this.linkeditor.currentPage - 2); i <= Math.min(this.linkeditor.currentPage + 6, FLUIDBOOK_DATA.settings.pages); i++) { + if ($('.preload[data-page="' + i + '"]').length >= 1) { + continue; + } + this.preloadTimeouts.push(setTimeout(function () { + var c = $('
'); + $("#linkeditor-preload").append(c); + $this._loadPage(i, c, true); + }, j * 1500)); + j++; + } + }, + + _loadPage: function (p, container) { + var imageFormat = FLUIDBOOK_DATA.settings.imageFormat; + var c = '
'; + if (this.linkeditor.utils.isSpecialPage(p)) { + let data = this.linkeditor.utils.getSpecialPageAssetData(p); + c += ''; + } else { + if (this.pagesSource === 'pages') { + if (this.rasterizePages.indexOf(p) >= 0) { + c += ''; + } else if (this.vectorPages.indexOf(p) >= 0) { + c += ''; + } else { + c += ''; + c += ''; + } + } else if (this.pagesSource === 'thumbnails') { + c += ''; + } + } + c += '
'; + $(container).html(c); + }, +} +export default LinkeditorLoader; diff --git a/resources/linkeditor-stable/js/linkeditor.panels.js b/resources/linkeditor-stable/js/linkeditor.panels.js new file mode 100644 index 000000000..66c6d5ef4 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.panels.js @@ -0,0 +1,169 @@ +function LinkeditorPanels(linkeditor) { + this.linkeditor = linkeditor; + + this.maxWidth = 70; + this.minWidth = 7; + this.defaultWidth = 15; + this.thresholdToggle = 3; +} + +LinkeditorPanels.prototype = { + init: function () { + var $this = this; + + this.sides = this.linkeditor.settings.get('panels_layout', {left: ['layers', 'versions'], right: ['form']}); + $.each(this.sides, function (side, panels) { + var panelsContainer = $("#linkeditor-" + side + '-panel'); + panelsContainer.data('width', $this.normalizeWidth($this.linkeditor.settings.get(side + '_width', $this.defaultWidth))); + + + $.each(panels, function (k, panel) { + $("#linkeditor-" + side + " nav").append($("#linkeditor-icon-" + panel)); + if ($('#linkeditor-panel-' + panel).length > 0) { + panelsContainer.append($('#linkeditor-panel-' + panel)); + } else { + panelsContainer.append('
'); + } + $('#linkeditor-panel-' + panel).attr('data-panel', panel).addClass('linkeditor-panel'); + + }); + + $('#linkeditor-' + side + ' nav a').attr('draggable', false); + + var tool = $this.linkeditor.settings.get(side + '_tool', panels[0]); + var open = $this.linkeditor.settings.get(side + '_open', side === 'right'); + + if (open) { + $this.setPanelState(tool, true); + } + }); + + this.linkeditor.layers.init(); + + this.linkeditor.resize.resize(); + $(document).on('mousedown', ".linkeditor-sidebar .handle", function (e) { + $this.linkeditor.setMouseCoordinates(e); + $(this).addClass('dragging'); + return false; + }); + + + }, + + moveHandle: function () { + var $this = this; + $(".linkeditor-sidebar .handle.dragging").each(function () { + let cssWidth; + let sidebar = $(this).closest('.linkeditor-sidebar'); + let side = sidebar.data('side'); + if (side === 'left') { + cssWidth = $this.linkeditor.mx - 49; + } else { + cssWidth = $this.linkeditor.resize.ww - 49 - $this.linkeditor.mx; + } + let panel = $(sidebar).find('.linkeditor-panel-side'); + let relativeWidth = 100 * (cssWidth / $this.linkeditor.resize.ww); + + let tool = $this.linkeditor.settings.get(side + '_tool'); + // Open/close when drag&drop + if (relativeWidth < $this.thresholdToggle) { + // Close + $this.setPanelState(tool, false); + } else { + $this.setPanelState(tool, true); + } + + let width = $this.normalizeWidth(relativeWidth); + panel.data('width', width); + $this.linkeditor.settings.set(side + '_width', width); + $this.linkeditor.resize.resize(); + }); + }, + + normalizeWidth: function (w) { + if (w === undefined || w === null || w === '') { + w = this.defaultWidth; + } + return Math.max(this.minWidth, Math.min(w, this.maxWidth)); + }, + + mouseup: function () { + $(".linkeditor-sidebar .handle.dragging").removeClass('dragging'); + }, + + resize: function (ww) { + $('.linkeditor-panel-side.open').each(function () { + var dw = parseFloat($(this).data('width')); + if (isNaN(dw)) { + return; + } + var w = (ww / 100) * dw; + $(this).css('width', w); + }); + this.linkeditor.versions.resize(); + }, + + toggleVersions: function () { + this.togglePanel('versions'); + }, + toggleLayers: function () { + this.togglePanel('layers'); + }, + toggleForm: function () { + this.togglePanel('form'); + }, + togglePanel: function (panel) { + this.setPanelState(panel, 'toggle'); + this.linkeditor.layers.update(); + }, + setPanelState: function (panel, newState) { + var $this = this; + var panelDiv = $('#linkeditor-panel-' + panel); + let container = panelDiv.closest('.linkeditor-panel-side'); + let side = panelDiv.closest('[data-side]').data('side'); + var currentState = panelDiv.hasClass('open'); + if (currentState === newState) { + return; + } + if (newState === 'toggle') { + newState = !currentState; + } + var icon = $("#linkeditor-icon-" + panel); + + + if (newState) { + icon.addClass('active'); + panelDiv.addClass('open'); + } else { + icon.removeClass('active'); + panelDiv.removeClass('open'); + } + + // Close panels on the same side + if (newState) { + $(icon).closest('nav').find('[data-panel]').each(function () { + if ($(this).data('panel') === panel) { + return; + } + $this.setPanelState($(this).data('panel'), false); + }); + } + + // Check if a panel is open on this side, if not, hide the panel + let sideOpen = $(container).children('.open').length > 0; + if (sideOpen) { + container.addClass('open'); + } else { + container.removeClass('open'); + } + + if (newState) { + this.linkeditor.settings.set(side + '_tool', panel); + } + this.linkeditor.layers.update(); + this.linkeditor.settings.set(side + '_open', newState); + this.linkeditor.resize.resize(); + }, + +}; +export default LinkeditorPanels; diff --git a/resources/linkeditor-stable/js/linkeditor.popup.js b/resources/linkeditor-stable/js/linkeditor.popup.js new file mode 100644 index 000000000..58d0a262d --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.popup.js @@ -0,0 +1,53 @@ +function LinkeditorPopup(linkeditor) { + this.linkeditor = linkeditor; + this.init(); +} + +LinkeditorPopup.prototype = { + init: function () { + var $this = this; + $(document).on('click', '.popup .close', function () { + if ($this.hasOpenPopup()) { + $this.close(); + } + }); + }, + + openLinksMove() { + this.open('moveLinks'); + }, + + open: function (name) { + var clone = $("#popup-templates [data-popup=" + name + "]").clone(); + $("#popup-holder").append(clone); + $("#popup-overlay").addClass('show'); + this.resize(); + }, + + close: function (name = '') { + $("#popup-overlay").removeClass('show'); + $("#popup-holder").html(''); + }, + + hasOpenPopup() { + return $("#popup-overlay.show").length === 1; + }, + + resize: function () { + if (!this.hasOpenPopup()) { + return; + } + var p = $("#popup-holder>div"); + var w = $(p).outerWidth(); + var h = $(p).outerHeight(); + var left = (this.linkeditor.resize.ww - w) / 2; + var top = (this.linkeditor.resize.hh - h) / 2; + $("#popup-holder").css({ + width: w, + height: h, + left: left, + top: top, + }) + } +}; +export default LinkeditorPopup; diff --git a/resources/linkeditor-stable/js/linkeditor.resize.js b/resources/linkeditor-stable/js/linkeditor.resize.js new file mode 100644 index 000000000..78912165a --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.resize.js @@ -0,0 +1,117 @@ +function LinkeditorResize(linkeditor) { + this.linkeditor = linkeditor; + this.init(); +} + +LinkeditorResize.prototype = { + init: function () { + var $this = this; + $(window).on('resize', function () { + $this.resize(); + setTimeout(function () { + $this.resize(); + }, 100); + }); + this.updateWindowDimensions(); + }, + + updateWindowDimensions: function () { + this.ww = $(window).outerWidth(); + this.hh = $(window).outerHeight(); + + }, + + resize: function () { + + let special = this.linkeditor.utils.isSpecialPage(); + if (this.linkeditor.single || special) { + $("#linkeditor").addClass('single').removeClass('double'); + } else { + $("#linkeditor").addClass('double').removeClass('single'); + } + + this.resizePages(); + this.updateWindowDimensions(); + this.resizeMain(); + this.resizeCanvas(); + if (this.linkeditor.panels) { + this.linkeditor.panels.resize(this.ww); + } + this.linkeditor.rulers.updateRulers(); + if (this.linkeditor.popup) { + this.linkeditor.popup.resize(); + } + }, + + resizePages: function () { + let $this = this; + let dimCover = $this.linkeditor.utils.getPageDimensions(1); + let pw; + let ph; + let special = false; + + $(".linkeditor-page[data-page]:visible").each(function () { + let p = $(this).attr('data-page'); + let dim = $this.linkeditor.utils.getPageDimensions(p); + if (dim === undefined) { + dim = dimCover; + } + pw = dim[0]; + ph = dim[1]; + + if ($this.linkeditor.utils.isSpecialPage(p)) { + special = true; + } else if ($this.linkeditor.mobileFirst) { + pw = dimCover[0]; + } else { + pw = dimCover[0]; + ph = dimCover[1]; + } + $(this).css({width: pw, height: ph}); + }); + + let fw = pw; + if (!this.linkeditor.single && !special) { + fw *= 2; + } + $("#linkeditor-page-right").css({left: this.linkeditor.pw}); + $("#linkeditor-fluidbook").css({width: fw, height: ph}); + + }, + + resizeMain: function () { + $("#linkeditor-main").css('width', this.ww - $('#linkeditor-left').outerWidth() - $('#linkeditor-right').outerWidth()); + }, + + resizeCanvas: function () { + this.linkeditor.canvasRect = $("#linkeditor-canvas").get(0).getBoundingClientRect(); + this.linkeditor.editorRect = $("#linkeditor-editor").get(0).getBoundingClientRect(); + var aw = this.linkeditor.canvasRect.width - 30; + var ah = this.linkeditor.canvasRect.height - 30; + + if (this.linkeditor.utils.isSpecialPage()) { + let dim = this.linkeditor.utils.getPageDimensions(); + this.linkeditor.fs = Math.min(1, aw / dim[0], ah / dim[1]); + } else if (this.linkeditor.mobileFirst) { + this.linkeditor.fs = 620 / this.linkeditor.fw; + } else { + this.linkeditor.fs = Math.min(aw / this.linkeditor.fw, ah / this.linkeditor.fh); + } + + let left, top; + + if (this.linkeditor.utils.isSpecialPage()) { + let dim = this.linkeditor.utils.getPageDimensions(); + left = (this.linkeditor.canvasRect.width - (dim[0] * this.linkeditor.fs)) / 2; + top = (this.linkeditor.canvasRect.height - (dim[1] * this.linkeditor.fs)) / 2; + } else if (!this.linkeditor.mobileFirst) { + left = ((this.linkeditor.canvasRect.width * 2) - this.linkeditor.fw * this.linkeditor.fs) / 2; + top = ((this.linkeditor.canvasRect.height * 2) - this.linkeditor.fh * this.linkeditor.fs) / 2; + } else { + left = (this.linkeditor.canvasRect.width - this.linkeditor.fw * this.linkeditor.fs) / 2; + top = 75; + } + $("#linkeditor-fluidbook").css({left: left, top: top, transform: 'scale(' + this.linkeditor.fs + ')'}); + }, +}; +export default LinkeditorResize; diff --git a/resources/linkeditor-stable/js/linkeditor.rulers.js b/resources/linkeditor-stable/js/linkeditor.rulers.js new file mode 100644 index 000000000..4de32bdae --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.rulers.js @@ -0,0 +1,251 @@ +function LinkeditorRulers(linkeditor) { + this.linkeditor = linkeditor; + + this.movingRuler = null; + this.rulersMagnetValuesX = []; + this.rulersMagnetValuesY = []; + this.dividers = [1, 2, 5, 10, 20, 50, 100, 200, 500]; + + this.init(); +} + +LinkeditorRulers.prototype = { + init: function () { + let $this = this; + $("#linkeditor-canvas").on('scroll', function () { + $this.updateRulers(); + }); + + $("#linkeditor-ruler-x").on('mousedown', function (e) { + $this.addRuler('y'); + return false; + }); + + $("#linkeditor-ruler-y").on('mousedown', function (e) { + $this.addRuler('x'); + return false; + }); + + $(document).on('mousedown', ".ruler", function (e) { + $this.movingRuler = $(this); + return false; + }); + }, + + mouseUp: function () { + this.stopMoveRuler(); + }, + + loadRulers: function (page, side) { + let $this = this; + $.each(this.getRulersOfPage(page, side), function (uid, ruler) { + $this.addRuler(ruler.type, ruler.pos, ruler.uid); + }); + this.linkeditor.links.updateMagnetValues(); + }, + + getRulersOfPage(page, side) { + if (page === undefined) { + page = this.linkeditor.currentPage; + } + var res = {}; + $.each(RULERS, function (uid, ruler) { + if (page != ruler.page) { + return; + } + res[uid] = ruler; + }); + return res; + }, + + updateRulers: function () { + // Update rects + this.linkeditor.fluidbookRect = $("#linkeditor-fluidbook").get(0).getBoundingClientRect(); + + $("#linkeditor-ruler-y,#linkeditor-ruler-x").html(''); + // Measure of visible fluidbook px at current zoom + + let factor = this.linkeditor.fs / this.linkeditor.zoom.zoom; + let visible_w = (this.linkeditor.canvasRect.width / this.linkeditor.fs) / this.linkeditor.zoom.zoom; + let visible_h = (this.linkeditor.canvasRect.height / this.linkeditor.fs) / this.linkeditor.zoom.zoom; + + // Find the best divider to have around 12 main divisions + let divider = 0; + for (let d in this.dividers) { + divider = this.dividers[d]; + let v = visible_h / divider; + if (v <= 12) { + break; + } + } + + let divisionSize = divider * this.linkeditor.fs * this.linkeditor.zoom.zoom; + // Draw vertical ruler + let margin = 100; + let nbDivisions = Math.floor(visible_h / divider); + let y0 = 16 + this.linkeditor.fluidbookRect.y - this.linkeditor.canvasRect.y; + + let yruler = '
1234.12
'; + for (let y = -margin; y <= nbDivisions + (margin * 2) + 1; y++) { + // Draw subdivision + let v = divider * y; + let ystart = y0 + (y * divisionSize); + if (ystart + divisionSize < 0 || ystart > this.linkeditor.canvasRect.height) { + continue; + } + yruler += '
' + Math.abs(v) + '
'; + for (let j = 1; j <= 9; j++) { + yruler += '
'; + } + yruler += '
'; + } + $("#linkeditor-ruler-y").html(yruler); + + // Draw horizontal ruler + nbDivisions = Math.floor(visible_w / divider); + let x0 = 16 + this.linkeditor.fluidbookRect.x - this.linkeditor.canvasRect.x; + let xruler = '
'; + + for (let x = -margin; x <= nbDivisions + (margin * 2) + 1; x++) { + // Draw subdivision + xruler += this._drawHorizontalSubdiv(x, x0, divider, divisionSize); + } + // Draw right page horizontal ruler + if (!this.linkeditor.single) { + x0 = x0 + this.linkeditor.pw * this.linkeditor.fs * this.linkeditor.zoom.zoom; + for (let x = 0; x <= nbDivisions + margin + 1; x++) { + // Draw subdivision + xruler += this._drawHorizontalSubdiv(x, x0, divider, divisionSize); + } + } + $("#linkeditor-ruler-x").html(xruler); + this.linkeditor.updateFBElements(true); + this.updateMousePositionRulers(); + }, + + _drawHorizontalSubdiv: function (x, x0, divider, divisionSize) { + let v = divider * x; + let xstart = x0 + (x * divisionSize); + if (xstart + divisionSize < 0 || xstart > this.linkeditor.canvasRect.width) { + return ''; + } + let res = '
' + Math.abs(v) + '
'; + for (let i = 1; i <= 9; i++) { + let cls = ''; + if (i === 5) { + cls += ' middle'; + } + res += '
'; + } + res += '
'; + return res; + }, + + updateMagnetValues: function () { + var $this = this; + this.rulersMagnetValuesX = [0, this.linkeditor.pw, this.linkeditor.pw * 2]; + this.rulersMagnetValuesY = [0, this.linkeditor.ph]; + $.each(this.linkeditor.links.getLinksOfPage(this.linkeditor.currentPage), function (uid, link) { + const left = parseFloat(link.left); + const top = parseFloat(link.top); + const width = parseFloat(link.width); + const height = parseFloat(link.height); + $this.rulersMagnetValuesX.push(left, left + width); + $this.rulersMagnetValuesY.push(top, top + height); + }); + }, + + updateMousePositionRulers: function () { + let rulersRect = $("#linkeditor-rulers").get(0).getBoundingClientRect(); + let rx = this.linkeditor.mx - rulersRect.x; + let ry = this.linkeditor.my - rulersRect.y; + $("#linkeditor-ruler-x .info").css('left', rx); + $("#linkeditor-ruler-y .info").css('top', ry); + + let rrect = $("#linkeditor-rulers").get(0).getBoundingClientRect(); + + if (this.linkeditor.mx - rrect.x < 0 || this.linkeditor.my - rrect.y < 0 || this.linkeditor.mx - rrect.x > rrect.width || this.linkeditor.my - rrect.y > rrect.height) { + $("#linkeditor-rulers .info").hide(); + return; + } + + let fb = this.linkeditor.globalToFluidbook(this.linkeditor.mx, this.linkeditor.my, true); + $("#linkeditor-ruler-y .info span").text(fb.y.toFixed(2)); + $("#linkeditor-ruler-x .info span").text(fb.x.toFixed(2)); + $("#linkeditor-rulers .info").css('display', 'inline-block'); + }, + + stopMoveRuler: function () { + if (this.movingRuler === null || this.movingRuler === undefined) { + return; + } + this.moveRuler(); + if ($(this.movingRuler).hasClass('pending-delete')) { + this.deleteRuler($(this.movingRuler)); + } + this.movingRuler = null; + }, + + moveRuler: function () { + if (this.movingRuler === null || this.movingRuler === undefined) { + return; + } + let magnet = !key.ctrl; + let editorMouse = this.linkeditor.globalToEditor(this.linkeditor.mx, this.linkeditor.my); + let fbMouse = this.linkeditor.globalToFluidbook(this.linkeditor.mx, this.linkeditor.my, false); + let css = {}; + let attrs = {'fb-update': '1'}; + let v, fbv; + if ($(this.movingRuler).data('axis') === 'x') { + v = editorMouse.x; + fbv = attrs['fb-left'] = magnet ? this.linkeditor.utils.magnetize(fbMouse.x, this.rulersMagnetValuesX) : fbMouse.x; + } else { + v = editorMouse.y; + fbv = attrs['fb-top'] = magnet ? this.linkeditor.utils.magnetize(fbMouse.y, this.rulersMagnetValuesY) : fbMouse.y; + } + if (v < 16) { + $(this.movingRuler).addClass('pending-delete'); + } else { + $(this.movingRuler).removeClass('pending-delete'); + } + $(this.movingRuler).css(css).attr(attrs); + RULERS[$(this.movingRuler).data('uid')].pos = fbv; + this.linkeditor.updateFBElements(); + this.linkeditor.links.updateMagnetValues(); + }, + + addRuler: function (axis, pos, uid) { + + if (undefined === uid) { + uid = this.linkeditor.utils.generateUID(); + RULERS[uid] = {page: this.linkeditor.currentPage, type: axis, uid: uid}; + this.linkeditor.hasChanged(); + } + let ruler = $('
'); + if (pos === undefined) { + this.movingRuler = ruler; + } else { + let dim = axis == 'x' ? 'left' : 'top'; + $(ruler).attr('fb-' + dim, pos); + } + + $("#linkeditor-editor").append(ruler); + this.moveRuler(); + }, + + deleteRuler: function (ruler) { + if (ruler === undefined) { + ruler = this.movingRuler; + } + delete RULERS[$(ruler).data('uid')]; + $(ruler).remove(); + this.movingRuler = null; + this.linkeditor.links.updateMagnetValues(); + this.linkeditor.hasChanged(); + }, + + clear: function () { + $('#linkeditor-editor .ruler').remove(); + }, +}; +export default LinkeditorRulers; diff --git a/resources/linkeditor-stable/js/linkeditor.save.js b/resources/linkeditor-stable/js/linkeditor.save.js new file mode 100644 index 000000000..6f9dbc96e --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.save.js @@ -0,0 +1,90 @@ +function LinkeditorSave(linkeditor) { + this.linkeditor = linkeditor; + this.init(); +} + +LinkeditorSave.prototype = { + init: function () { + let $this = this; + + this.automaticSaveFrequency = 1000 * 5 * 60; + this.unsavedChanges = false; + this.automaticSaveTimeout = null; + this.runningAutomaticSaveTimeout = false; + + $(window).on('beforeunload', function () { + if ($this.unsavedChanges) { + return TRANSLATIONS.warning_unsaved_changes; + } + }) + }, + + hasChanged: function () { + let $this = this; + this.unsavedChanges = true; + if (this.runningAutomaticSaveTimeout === false) { + this.runningAutomaticSaveTimeout = true; + this.automaticSaveTimeout = setTimeout(function () { + $this.automaticSave(); + }, this.automaticSaveFrequency); + } + }, + + saveIfUnsavedChanges: function (message, notify, callback) { + if (this.unsavedChanges) { + this.save(message, false, function () { + setTimeout(function () { + callback(); + }, 1000); + }); + } else { + callback(); + } + }, + + save: function (message, notify, callback) { + if (notify === undefined) { + notify = true; + } + if (callback === undefined) { + callback = function () { + + }; + } + var $this = this; + if (message === undefined) { + message = TRANSLATIONS.manual_save_message; + } + + $.ajax({ + url: '/fluidbook-publication/' + FLUIDBOOK_DATA.id + '/save/links', method: 'post', data: { + _method: 'put', + message: message, + rulers: JSON.stringify(window.RULERS), + links: JSON.stringify(window.LINKS), + }, + success: function (data) { + if (notify) { + $this.linkeditor.notification(TRANSLATIONS.success_save); + } + clearTimeout($this.automaticSaveTimeout); + $this.unsavedChanges = false; + $this.runningAutomaticSaveTimeout = false; + + window.ASSETS = data.assets; + $this.linkeditor.versions.setVersions(data.versions); + callback(); + }, + error: function (jqXHR, status, error) { + $this.linkeditor.hasChanged(); + $this.linkeditor.notification(TRANSLATIONS.error_save + ' : ' + error, 'error'); + }, + }); + }, + + automaticSave: function () { + this.save(TRANSLATIONS.automatic_save_message); + }, +}; + +export default LinkeditorSave; diff --git a/resources/linkeditor-stable/js/linkeditor.settings.js b/resources/linkeditor-stable/js/linkeditor.settings.js new file mode 100644 index 000000000..1ba80adbc --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.settings.js @@ -0,0 +1,40 @@ +function LinkeditorSettings(linkeditor) { + this.linkeditor = linkeditor; + this.saveTimeout = null; +} + +LinkeditorSettings.prototype = { + save: function () { + var data = {}; + $.each(SETTINGS, function (k, v) { + data['linkeditor_' + k] = v; + }); + + $.ajax({ + url: '/toolbox_setting', method: 'POST', data: {settings: data}, + }); + }, + + get: function (key, defaultValue) { + var res = SETTINGS[key]; + if (res === 'true') { + res = true; + } else if (res === 'false') { + res = false; + } + if (res === undefined || res === null) { + return defaultValue; + } + return res; + }, + + set: function (key, value) { + let $this = this; + SETTINGS[key] = value; + clearTimeout(this.saveTimeout); + this.saveTimeout = setTimeout(function () { + $this.save(); + }, 3000); + } +}; +export default LinkeditorSettings; diff --git a/resources/linkeditor-stable/js/linkeditor.toolbar.js b/resources/linkeditor-stable/js/linkeditor.toolbar.js new file mode 100644 index 000000000..bf6d6205b --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.toolbar.js @@ -0,0 +1,87 @@ +function LinkeditorToolbar(linkeditor) { + this.linkeditor = linkeditor; + this.init(); +} + +LinkeditorToolbar.prototype = { + init: function () { + var $this = this; + $("#linkeditor-page-field input").on('change', function () { + $this.linkeditor.changePage($(this).val()); + $(this).blur(); + return false; + }); + + $("[data-key]").each(function () { + let e = $(this); + key($(this).data('key'), function () { + if ($(e).is('[data-key-skipintextfields]') && $this.linkeditor.utils.isfocusOnFormItem()) { + return true; + } + $(e).addClass('hover'); + $this.linkeditor.runAction($(e).data('action')); + setTimeout(function () { + $(e).removeClass('hover') + }, 150); + return false; + }); + }); + + $(document).on('click', '[data-action]', function () { + $this.linkeditor.runAction($(this).data('action'), $(this).is('[data-action-args]') ? $(this).data('action-args') : []); + return false; + }); + + $(document).on('submit', 'form.reloadAfterSuccess', function () { + $this.submitFormAndReload(this); + return false; + }); + + $(document).on('change', '.importExcel input', function () { + $this.submitFormAndReload($(this).closest('form')); + return false; + }); + + $(document).on('click', '#linkeditor-export-latest', function () { + var e = $(this); + if ($this.linkeditor.save.unsavedChanges) { + $this.linkeditor.save.save(TRANSLATIONS.before_export_save_message, false, function () { + $(e).get(0).click(); + }); + return false; + } + return true; + }) + + this.linkeditor.initTooltips(); + + $('#linkeditor-toolbar a').attr('draggable', false); + }, + + submitFormAndReload: function (form) { + this.linkeditor.displayLoader(); + var $this = this; + var callback = function () { + $(form).ajaxSubmit( + { + dataType: 'json', + success: function (data) { + setTimeout(function () { + window.location.reload(); + }, 1000); + }, error: function (data) { + $this.linkeditor.hideLoader(); + $this.linkeditor.notification(TRANSLATIONS.error + ' : ' + data.responseJSON.message, 'error'); + }, + } + ); + } + + if ($(form).is('[data-save-before-submit]')) { + this.linkeditor.save.saveIfUnsavedChanges($(form).attr('data-save-before-submit'), false, callback) + } else { + callback(); + } + }, +}; +export default LinkeditorToolbar; diff --git a/resources/linkeditor-stable/js/linkeditor.undo.js b/resources/linkeditor-stable/js/linkeditor.undo.js new file mode 100644 index 000000000..bbbbf24e1 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.undo.js @@ -0,0 +1,130 @@ +function LinkeditorUndo(linkeditor) { + this.linkeditor = linkeditor; + this.ignoreStatesChanges = false; + this.states = []; + this.indexes = []; + this.init(); +} + +LinkeditorUndo.prototype = { + init: function () { + }, + + initState: function () { + if (this.states[this.linkeditor.getCurrentPage()] === undefined || this.states[this.linkeditor.getCurrentPage()] === null) { + this.indexes[this.linkeditor.getCurrentPage()] = 0; + this.pushState(); + } + }, + + _states: function () { + let redo = false; + let undo = false; + let nb = this.states[this.linkeditor.getCurrentPage()].length; + let idx = this.indexes[this.linkeditor.getCurrentPage()]; + if (nb > 1) { + if (idx < nb) { + redo = true; + } + if (idx > 1) { + undo = true; + } + } + return {redo: redo, undo: undo, index: idx, nb: nb}; + }, + + updateIconsStates: function () { + let s = this._states(); + if (s.redo) { + $('[data-icon=redo]').removeClass('disabled'); + } else { + $('[data-icon=redo]').addClass('disabled'); + } + + if (s.undo) { + $('[data-icon=undo]').removeClass('disabled'); + } else { + $('[data-icon=undo]').addClass('disabled'); + } + }, + + canRedo: function () { + return this._states().redo; + }, + + canUndo: function () { + return this._states().undo; + }, + + + pushState: function () { + if (this.ignoreStatesChanges) { + console.log('ignore states changes'); + return; + } + + let index = this.indexes[this.linkeditor.getCurrentPage()]; + if (index === 0) { + this.states[this.linkeditor.getCurrentPage()] = []; + } + + let cs = this.linkeditor.links.getCurrentState(); + let ps = this.states[this.linkeditor.getCurrentPage()][index - 1]; + if (ps == cs) { + // console.log('skipped : no change'); + return; + } + + if (index > 0 && index < this.states[this.linkeditor.getCurrentPage()].length) { + this.states[this.linkeditor.getCurrentPage()] = this.states[this.linkeditor.getCurrentPage()].slice(0, index); + } + this.states[this.linkeditor.getCurrentPage()].push(cs); + this.indexes[this.linkeditor.getCurrentPage()]++; + + //console.log('push current index', index, 'states length', this.states[this.linkeditor.getCurrentPage()].length); + + this.updateIconsStates(); + }, + + undo: function () { + if (!this.canUndo()) { + return; + } + let index = this.indexes[this.linkeditor.getCurrentPage()]; + index--; + let state = this.states[this.linkeditor.getCurrentPage()][index - 1]; + this.ignoreStatesChanges = true; + this.linkeditor.links.setCurrentState(state); + var $this = this; + setTimeout(function () { + $this.ignoreStatesChanges = false; + }, 500); + this.indexes[this.linkeditor.getCurrentPage()] = index; + + //console.log('undo : current index', index, 'states length', this.states[this.linkeditor.getCurrentPage()].length); + + this.updateIconsStates(); + }, + + redo: function () { + if (!this.canRedo()) { + return; + } + let index = this.indexes[this.linkeditor.getCurrentPage()]; + let state = this.states[this.linkeditor.getCurrentPage()][index]; + this.ignoreStatesChanges = true; + this.linkeditor.links.setCurrentState(state); + var $this = this; + setTimeout(function () { + $this.ignoreStatesChanges = false; + }, 500); + index++; + this.indexes[this.linkeditor.getCurrentPage()] = index; + //console.log('redo : current index', index, 'states length', this.states[this.linkeditor.getCurrentPage()].length); + + this.updateIconsStates(); + } +} + + +export default LinkeditorUndo; diff --git a/resources/linkeditor-stable/js/linkeditor.utils.js b/resources/linkeditor-stable/js/linkeditor.utils.js new file mode 100644 index 000000000..de2dc5075 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.utils.js @@ -0,0 +1,173 @@ +var LinkeditorUtils = function (linkeditor) { + this.linkeditor = linkeditor; + this.init(); +}; + +LinkeditorUtils.prototype = { + init: function () { + + }, + + maxPageHeight: function () { + + }, + + + array_unique:function(arr){ + return arr.filter((value, index, array) => array.indexOf(value) === index); + }, + + isSpecialPage: function (page) { + if (page === undefined) { + page = this.linkeditor.currentPage; + } + return page.toString().indexOf('link_') === 0 || (THEME[page] !== undefined && THEME[page] !== null); + }, + + getSpecialPageAssetData: function (page) { + if (page.toString().indexOf('link_') === 0) { + let asset = this.getLinkImageId(page); + return ASSETS[asset]; + } else { + return THEME[page]; + } + }, + + getSpecialPageAssetURL: function (page) { + return this.getSpecialPageAssetData(page).url; + }, + + getLinkImageId(page) { + return page.toString().substring(5); + }, + + getPageDimensions(page) { + if (page === undefined) { + page = this.linkeditor.currentPage; + } + if (this.isSpecialPage(page)) { + if (page.toString().indexOf('link_') === 0) { + return ASSETS[this.getLinkImageId(page)]['dim']; + } else { + return THEME[page]['dim']; + } + } else { + page = parseInt(page); + } + return FLUIDBOOK_DATA.page_dimensions[page]; + }, + + normalizePage: function (page) { + if (this.isSpecialPage(page)) { + return page; + } + page = parseInt(page); + if (page % 2 === 1 && !this.linkeditor.single) { + page--; + } + return Math.max(this.linkeditor.single ? 1 : 0, Math.min(page, FLUIDBOOK_DATA.settings.pages)); + }, + + splitPages: function (str) { + let res = []; + if (str === undefined || str === null) { + return res; + } + str = str.toString(); + if (str == '') { + return res; + } + let pages = str.split(','); + for (let p in pages) { + res.push(parseInt(pages[p])); + } + return res; + }, + + pct: function (v) { + return (v * 100) + '%'; + }, + + generateUID: function () { + const length = 12; + let result = ''; + const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; + }, + + magnetize: function (value, values, width, onlyCheckOtherSide) { + if (onlyCheckOtherSide === undefined) { + onlyCheckOtherSide = false; + } + if (width === undefined) { + width = 0; + } + let sensibility = 8 / (this.linkeditor.zoom.zoom * this.linkeditor.fs); + let min = 100000; + let magnetValue; + let magnetizeOtherSide = false; + + if (values.length === 0) { + return value; + } + + for (let i in values) { + let v = values[i]; + + if (!onlyCheckOtherSide) { + let diff = Math.abs(v - value); + if (diff < min) { + min = diff; + magnetValue = v; + magnetizeOtherSide = false; + } + } + + let diffOtherSide = Math.abs(v - (value + width)); + if (diffOtherSide < min) { + min = diffOtherSide; + magnetValue = v; + magnetizeOtherSide = true; + } + } + + if (min > sensibility) { + return value; + } + if (magnetizeOtherSide) { + return magnetValue - width; + } else { + return magnetValue; + } + }, + + roundDimension: function (v) { + return (Math.round(v * 100000) / 100000); + }, + + intersectRect: function (r1, r2) { + return !(r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top); + }, + + isfocusOnFormItem: function () { + return $(document.activeElement).is('input[type="text"],input[type="email"],input[type="number"],input[type="tel"],input[type="search"],textarea,select'); + }, + + isFirefox: function () { + return navigator.userAgent.search("Firefox") >= 0; + }, + + isSafari: function () { + return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + }, + + isWindows: function () { + return navigator.userAgent.search('Windows') >= 0; + } +}; + +export default LinkeditorUtils; diff --git a/resources/linkeditor-stable/js/linkeditor.versions.js b/resources/linkeditor-stable/js/linkeditor.versions.js new file mode 100644 index 000000000..4b9888e07 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.versions.js @@ -0,0 +1,81 @@ +function LinkeditorVersions(linkeditor) { + this.linkeditor = linkeditor; + this.init(); +} + +LinkeditorVersions.prototype = { + init: function () { + this.refresh(); + }, + + refresh: function () { + var $this = this; + $.ajax({ + url: '/fluidbook-publication/' + FLUIDBOOK_DATA.id + '/edit/links/versions', method: 'get', + success: function (data) { + $this.setVersions(data); + } + }); + }, + + setVersions: function (data) { + var list = $("#linkeditor-panel-versions-list"); + list.html(''); + $.each(data, function (k, version) { + let actionArgs = JSON.stringify([version.timestamp]); + var item = '
'; + item += '
'; + item += '
' + version.date + '
'; + item += '
' + version.name + '
'; + item += '
' + version.comments + '
'; + item += '
'; + item += '
'; + item += ''; + item += '
' + version.rulers + ' rulers
'; + item += '
' + item += '
'; + item += '
' + + '
'; + item += '
' + item += '
' + list.append(item); + }); + this.linkeditor.initIcons(); + this.linkeditor.initTooltips(); + this.resize(); + }, + + restoreVersion: function (timestamp) { + var $this = this; + var callback = function () { + $this._restoreVersion(timestamp); + } + + //Restore links from 2022-12-07 13:37:15 + this.linkeditor.save.saveIfUnsavedChanges(TRANSLATIONS.before_restore_message,false,callback); + }, + + _restoreVersion(timestamp) { + $.ajax({ + url: '/fluidbook-publication/' + FLUIDBOOK_DATA.id + '/edit/links/versions/restore/' + timestamp + '', + success: function (data) { + window.location.reload(); + }, + }); + }, + + resize: function () { + var w = $("#linkeditor-panel-versions-list").outerWidth(); + if (w <= 0) { + return; + } + if (w < 300) { + $("#linkeditor-panel-versions-list").addClass('small'); + } else { + $("#linkeditor-panel-versions-list").removeClass('small'); + } + }, + + +}; +export default LinkeditorVersions; diff --git a/resources/linkeditor-stable/js/linkeditor.zoom.js b/resources/linkeditor-stable/js/linkeditor.zoom.js new file mode 100644 index 000000000..57dc66f92 --- /dev/null +++ b/resources/linkeditor-stable/js/linkeditor.zoom.js @@ -0,0 +1,143 @@ +function LinkeditorZoom(linkeditor) { + this.linkeditor = linkeditor; + this.init(); +} + +LinkeditorZoom.prototype = { + init: function () { + var $this = this; + + this.zoom = 1; + this.zoomdragging = false; + + if (!this.linkeditor.mobileFirst) { + $("#linkeditor-main").on('wheel', function (e) { + var step = $this.zoom >= 1 ? 0.25 : 0.1; + $this.linkeditor.setMouseCoordinates(e); + let cursorPositionOnFluidbookBeforeZoom = $this.getFluidbookPositionOfCursor(); + + let delta = e.originalEvent.deltaY; + if (delta === 0) { + return true; + } + e.stopPropagation(); + e.stopImmediatePropagation(); + e.preventDefault(); + + if ($this.setZoom($this.zoom + step * (delta > 0 ? -1 : 1))) { + $this.moveZoomAfterWheelZoom(cursorPositionOnFluidbookBeforeZoom); + } + + return false; + }); + } + }, + + getFluidbookPositionOfCursor: function () { + let rect = $("#linkeditor-fluidbook").get(0).getBoundingClientRect(); + let lx = (this.linkeditor.mx - rect.x) / rect.width; + let ly = (this.linkeditor.my - rect.y) / rect.height; + return {x: lx, y: ly, width: rect.width, height: rect.height}; + }, + + mouseUp: function () { + if (!this.zoomdragging) { + return; + } + this.moveZoomDrag(); + this.resetZoomDrag(); + }, + + updateMousePosition: function () { + if (!$("#linkeditor-main").hasClass('grab')) { + this.resetZoomDrag(); + } + if (this.zoomdragging !== false) { + this.moveZoomDrag(); + } + }, + + resetZoomDrag: function () { + $("#linkeditor-main").removeClass('grab').removeClass('grabbing'); + this.zoomdragging = false; + }, + + moveZoomDrag: function () { + let deltaX = this.linkeditor.mx - this.zoomdragging.x; + let deltaY = this.linkeditor.my - this.zoomdragging.y; + $("#linkeditor-canvas").scrollTo({ + top: this.zoomdragging.scrollY - deltaY, left: this.zoomdragging.scrollX - deltaX + }); + this.linkeditor.rulers.updateRulers(); + }, + + moveZoomAfterWheelZoom: function (initialPosition) { + let currentPosition = this.getFluidbookPositionOfCursor(); + var dx = (initialPosition.x - currentPosition.x) * currentPosition.width; + var sx = $("#linkeditor-canvas").scrollLeft() + dx; + + var dy = (initialPosition.y - currentPosition.y) * currentPosition.height; + var sy = $("#linkeditor-canvas").scrollTop() + dy; + + $("#linkeditor-canvas").scrollTo({left: sx, top: sy}); + this.linkeditor.rulers.updateRulers(); + }, + + + normalizeZoom: function (z) { + if (this.linkeditor.mobileFirst) { + return 1; + } + return Math.max(0.5, Math.min(8, z)); + }, + + setZoom: function (z, force) { + let $this = this; + z = this.normalizeZoom(z); + + this.zoom = z; + + let cw2, ch2, zh; + if (this.linkeditor.utils.isSpecialPage()) { + cw2 = this.linkeditor.canvasRect.width; + zh = this.linkeditor.canvasRect.height; + } else if (this.linkeditor.mobileFirst) { + cw2 = this.linkeditor.canvasRect.width; + zh = (this.linkeditor.getCurrentPageHeight() * this.linkeditor.fs) + 150; + } else { + cw2 = this.linkeditor.canvasRect.width * 2; + ch2 = this.linkeditor.canvasRect.height * 2; + zh = ch2; + if (this.zoom < 1) { + zh *= this.zoom; + } + } + + $("#linkeditor-canvas").attr('data-z', this.zoom); + $("#linkeditor-zoom").css({ + transform: 'scale(' + this.zoom + ')', overflow: 'visible', + width: cw2, + maxWidth: cw2, + minWidth: cw2, + height: zh, + minHeight: zh, + maxHeight: zh + }); + + setTimeout(function () { + if (this.zoom === 1) { + $this.resetZoomDrag(); + } + $this.linkeditor.rulers.updateRulers(); + }, 10); + return true; + }, + + reset: function () { + this.setZoom(1); + var top = this.linkeditor.mobileFirst ? 0 : '50%'; + $("#linkeditor-canvas").scrollTo({top: top, left: '50%'}); + this.resetZoomDrag(); + }, +}; +export default LinkeditorZoom; diff --git a/resources/linkeditor-stable/style/inc/_contextmenu.sass b/resources/linkeditor-stable/style/inc/_contextmenu.sass new file mode 100644 index 000000000..30c658236 --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_contextmenu.sass @@ -0,0 +1,11 @@ +.context-menu-list + z-index: 1000001 !important + +.context-menu-item + kbd + float: right + margin-left: 20px + border-radius: 2px + border: 1px solid currentColor + padding: 2px 4px + opacity: 0.6 diff --git a/resources/linkeditor-stable/style/inc/_form.sass b/resources/linkeditor-stable/style/inc/_form.sass new file mode 100644 index 000000000..9c6fe12f7 --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_form.sass @@ -0,0 +1,278 @@ +body + --form-text-color: #5d5d5d + --field-background: #fff + --field-color: #111 + --field-border: #aaa + + @include dark-theme + --form-text-color: #aaa + --field-background: #000 + --field-color: #ccc + --field-border: #777 + +textarea, input[type="text"], input[type="number"], input[type="email"], input[type="url"], input[type="tel"] + font-family: $font + font-weight: 400 + color: var(--field-color) + background-color: var(--field-background) + border: 1px solid var(--field-background) + border-radius: 3px + font-size: 13px + transition: box-shadow 500ms, border 500ms + appearance: none + + &:focus + border: 1px solid var(--field-border) + box-shadow: 0 0 8px rgba(0, 0, 0, 0.3) + +input[type="checkbox"] + margin-right: 8px + appearance: none + position: relative + cursor: pointer + color: var(--form-text-color) + + &::before + content: '' + display: inline-block + width: 14px + height: 14px + border: 1px solid currentColor + border-radius: 2px + vertical-align: baseline + + &:checked + &::after + content: '' + display: block + width: 12px + height: 12px + position: absolute + top: 1px + left: 1px + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%) + background-color: currentColor + + +input[type=number] + &, &:hover + appearance: textfield + + &::-webkit-inner-spin-button, &::-webkit-outer-spin-button + -webkit-appearance: none + margin: 0 + visibility: hidden + +button + font-family: $font + font-weight: 500 + font-size: 15px + padding: 5px 10px + border: 1px solid var(--field-border) + border-radius: 4px + color: var(--field-color) + background-color: var(--field-background) + cursor: pointer + +.select2-hidden-accessible + position: fixed !important + +#linkeditor-form-templates + display: none + +.linkeditor-linktype + &::before + display: inline-block + width: 12px + height: 12px + border-radius: 2px + margin: 2px 10px 0 0 + content: "" + vertical-align: top + position: relative + + +#linkeditor-panel-form + + + padding: 12px + font-size: 13px + + .select2-container--bootstrap + font-size: 13px + font-weight: 400 + + hr + border: 0 + height: 1px + background-color: var(--form-text-color) + margin: 5px 0 + + &, .select2-selection + color: var(--form-text-color) + background-color: var(--field-background) + border-color: var(--field-background) + border-radius: 4px + box-shadow: none + + &.select2-container--focus .select2-selection, &.select2-container--open .select2-selection + box-shadow: 0 0 8px rgba(0, 0, 0, 0.1) + border-color: var(--field-border) + + h3 + font-size: 16px + color: var(--form-text-color) + text-transform: uppercase + padding-top: 15px + border-top: 1px solid var(--form-text-color) + margin-top: 20px + + p.help-block + color: var(--form-text-color) + font-size: 11px + padding-top: 2px + white-space: normal + + .freefile-file + position: relative + + &.loading + &::after + background-image: url("/images/linkeditor/dots-animated.svg") + @include dark-theme + background-image: url("/images/linkeditor/dots-dark-animated.svg") + + input + pointer-events: none + cursor: wait + + &::after + content: "" + position: absolute + display: block + padding: 6px + right: 0 + top: 6px + width: 25px + height: 25px + background-image: url("/images/linkeditor/dots.svg") + color: var(--field-color) + box-sizing: border-box + pointer-events: none + font-size: 17px + @include dark-theme + background-image: url("/images/linkeditor/dots-dark.svg") + + input[type=file] + position: absolute + right: 0 + width: 35px + opacity: 0 + height: 100% + cursor: pointer + + a.upload + position: absolute + right: 0 + width: 35px + opacity: 0 + height: 100% + cursor: pointer + z-index: 2 + + + .input-group + position: relative + + .input-group-append + position: absolute + height: 100% + top: 0 + right: 0 + pointer-events: none + padding: 8px + + textarea, input[type="text"], input[type="number"], input[type="email"], input[type="url"], input[type="tel"] + height: 34px + padding: 8px + width: 100% + + textarea + height: auto + min-height: 150px + max-width: 100% + min-width: 100% + + span + font-weight: 400 + font-size: 13px + color: var(--form-text-color) + + label + display: block + font-weight: 600 + font-size: 12px + color: var(--form-text-color) + margin: 8px 0 5px 0 + + .checkbox + margin: 4px 0 5px 0 + + position: relative + top: 6px + + label + vertical-align: baseline + display: inline-block + position: relative + top: -4px + margin: 0 + cursor: pointer + + span + margin-left: 5px + + + #group_position, #group_dimensions, #group_transform + margin-top: 5px + + h4 + font-size: 12px + font-weight: 700 + color: var(--form-text-color) + grid-column: 1 / 3 + grid-row: 1 / 2 + margin-bottom: 5px + + display: grid + grid-template-columns: repeat( 2, 1fr) + grid-gap: 2px 25px + + label + display: inline-block + width: 20px + + input + width: calc(100% - 20px) + + [data-name="rot"] + label + display: block + + .input-group + .input-group-append + right: 0 + + .input-group + display: inline-block + + .input-group-append + right: 20px + + + #group_transform + input + width: 100% + +.select2-container--bootstrap .select2-results > .select2-results__options + max-height: 350px !important diff --git a/resources/linkeditor-stable/style/inc/_layers.sass b/resources/linkeditor-stable/style/inc/_layers.sass new file mode 100644 index 000000000..4ce410a4c --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_layers.sass @@ -0,0 +1,74 @@ +#linkeditor-panel-layers + font-size: 11px + color: $color + user-select: none + + @include dark-theme + color: $color-dark + + h3 + border-bottom: 1px solid currentColor + padding: 3px 10px + font-style: italic + font-weight: 500 + background-color: rgba(0, 0, 0, 0.45) + color: $color-dark + @include dark-theme + background-color: rgba(255, 255, 255, 0.45) + color: $color + + .layer + position: relative + + label + display: block + border-bottom: 1px solid currentColor + padding: 5px 10px + cursor: pointer + + + span + display: inline-block + position: absolute + right: 28px + background-color: rgba(255, 255, 255, 0.5) + color: #000 + border-radius: 2px + padding: 2px + font-family: "Courier New", Courier, monospace + + input + color: #fff + position: relative + top: 2px + + &::before + border: 0 + + &[data-locked="1"] + label + pointer-events: none + + a.lock + svg + display: block + + a.lock + display: block + position: absolute + top: 5px + right: 6px + border-radius: 2px + width: 16px + height: 16px + padding: 3px 4px 0 + color: rgba(0, 0, 0, 0.6) + background-color: rgba(0, 0, 0, 0.15) + + @include dark-theme + color: rgba(255, 255, 255, 0.8) + background-color: rgba(255, 255, 255, 0.25) + + + svg + display: none diff --git a/resources/linkeditor-stable/style/inc/_links.sass b/resources/linkeditor-stable/style/inc/_links.sass new file mode 100644 index 000000000..24c38cd6d --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_links.sass @@ -0,0 +1,103 @@ +@import "variables" + +#linkeditor-links + .link + + &[data-locked="1"] + pointer-events: none + + #linkeditor-main.grab & + pointer-events: none + + box-sizing: border-box + position: absolute + border: currentColor solid 1px + cursor: cell + + #linkeditor-main.dropfile &.dropfile + border-style: dashed + + &.dropfile.dragging + border-width: 3px + border-style: dashed + + &.pendingCreate + opacity: 0 + + &.selected + cursor: move + z-index: 1000000 !important + + .corners + visibility: visible + + &[fb-polygon^="["] + background-color: transparent !important + border-style: none + + &.pendingPolygonCreate + clip-path: none !important + + .corners + visibility: hidden + position: absolute + top: 0 + left: 0 + width: 100% + height: 100% + + > div + position: absolute + border: 1px solid currentColor + background-color: #fff + width: 8px + height: 8px + + &.nw + cursor: nw-resize + + &.n + cursor: n-resize + + &.ne + cursor: ne-resize + + &.e + cursor: e-resize + + &.se + cursor: se-resize + + &.s + cursor: s-resize + + &.sw + cursor: sw-resize + + &.w + cursor: w-resize + + &.n, &.nw, &.ne + top: -4px + + &.e, &.w + top: calc(50% - 4px) + + &.sw, &.s, &.se + bottom: -4px + + &.nw, &.w, &.sw + left: -4px + + &.ne, &.e, &.se + right: -4px + + &.n, &.s + left: calc(50% - 4px) + + &.poly + cursor: crosshair + border-radius: 50% + + position: relative + z-index: 500 diff --git a/resources/linkeditor-stable/style/inc/_mixins.sass b/resources/linkeditor-stable/style/inc/_mixins.sass new file mode 100644 index 000000000..3700d4a74 --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_mixins.sass @@ -0,0 +1,20 @@ +@mixin tinyscrollbars + &::-webkit-scrollbar + width: 6px + height: 6px + + &::-webkit-scrollbar-track + background: transparent + + &:hover + background-color: #000 + + &::-webkit-scrollbar-thumb + background-color: #aaa + border: 1px solid #333 + border-radius: 20px + +@mixin dark-theme + @media (prefers-color-scheme: dark) + & + @content diff --git a/resources/linkeditor-stable/style/inc/_panels.sass b/resources/linkeditor-stable/style/inc/_panels.sass new file mode 100644 index 000000000..5509acafd --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_panels.sass @@ -0,0 +1,91 @@ +.linkeditor-sidebar + min-width: $sidebar-icons-width + background-color: #EBECEE + position: relative + + @include dark-theme + background-color: #444 + + nav + position: absolute + left: 0 + width: $sidebar-icons-width + height: 100% + + [data-icon] + display: block + vertical-align: top + height: 30px + width: 30px + padding: 5px + border-radius: 5px + margin: 6px 0 0 7px + text-align: center + color: $toolbar-color + @include dark-theme + color: $toolbar-color-dark + + &:hover, &.hover, &.active + background-color: #fff + @include dark-theme + background-color: #000 + + svg + position: relative + top: 1px + height: 18px + width: auto + + + .handle + position: absolute + top: 0 + right: 0 + width: $sidebar-handle-width + height: 100% + background-color: #aaa + cursor: col-resize + z-index: 1 + + @include dark-theme + background-color: #666 + + + .linkeditor-panel-side + display: none + margin-left: $sidebar-icons-width + margin-right: $sidebar-handle-width + + overflow-x: hidden + @include tinyscrollbars + + background: $panel-background + height: 100% + @include dark-theme + background-color: #333 + + &.open + display: block + + .linkeditor-panel + display: none + &.open + display: block + + &#linkeditor-right + border-width: 0 0 0 2px + + nav + left: auto + right: 0 + + [data-icon] + margin-left: 9px + + .handle + right: auto + left: 0 + + .linkeditor-panel-side + margin-left: $sidebar-handle-width + margin-right: $sidebar-icons-width diff --git a/resources/linkeditor-stable/style/inc/_popup.sass b/resources/linkeditor-stable/style/inc/_popup.sass new file mode 100644 index 000000000..2dd32b77d --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_popup.sass @@ -0,0 +1,65 @@ +#popup-templates + display: none + +#popup-overlay + background-color: rgba(0, 0, 0, 0.5) + position: absolute + top: 0 + left: 0 + opacity: 0 + pointer-events: none + transition: opacity 350ms + width: 100% + height: 100% + z-index: 10000000 + + &.show, + &.unavailable + opacity: 1 + pointer-events: auto + + #popup-holder + position: absolute + max-width: 100% + max-height: 100% + + .popup + background-color: #dbdddf + color: #5D5D5D + font-size: 13px + padding: 20px + box-shadow: 0 0 20px rgba(0,0,0,0.5) + + h2 + font-size: 16px + padding-bottom: 10px + font-weight: 500 + + a.close + display: block + position: absolute + top: 20px + right: 20px + width: 15px + height: 15px + cursor: pointer + + p + margin: 12px 0 + + p.button + text-align: right + margin: 20px 0 0 0 + + + @include dark-theme + background-color: #333 + color: #d5d5d5 + + &[data-popup="moveLinks"] + input + &[type="text"],&[type="number"] + width: 50px + height: 20px + margin: 0 5px + padding: 4px diff --git a/resources/linkeditor-stable/style/inc/_rulers.sass b/resources/linkeditor-stable/style/inc/_rulers.sass new file mode 100644 index 000000000..9bdfe1767 --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_rulers.sass @@ -0,0 +1,198 @@ +@import "variables" + +.ruler + position: absolute + top: 0 + left: 0 + z-index: 400 + border-width: 0 + border-color: #0ff + border-style: solid + + &:after + position: absolute + content: "" + display: block + height: 100% + width: 100% + + &.pending-delete + border-color: #f00 !important + z-index: 1100 + + &:hover + border-color: #0f0 + + &[data-axis="x"] + width: 0px + height: calc(100% - $rulers-size) + border-left-width: 1px + cursor: col-resize + top: $rulers-size + + &:after + left: $ruler-margin*-1 + width: $ruler-margin*2 + + &[data-axis="y"] + border-bottom-width: 1px + height: 0px + width: calc(100% - $rulers-size) + left: $rulers-size + + &:after + top: $ruler-margin*-1 + height: $ruler-margin*2 + cursor: row-resize + +#linkeditor-rulers + $rulers-color: #333 + $rulers-color-dark: #eee + + @include dark-theme + color: $rulers-color-dark + + + color: $rulers-color + position: absolute + top: 0px + left: 0px + width: 100% + height: 100% + + #linkeditor-ruler-corner + position: absolute + top: 0px + left: 0px + width: $rulers-size + height: $rulers-size + z-index: 5 + background-color: #aaa + @include dark-theme + background-color: #666 + + + .ruler-bar + overflow: hidden + position: absolute + left: 0 + top: 0 + z-index: 1000 + + .info + position: absolute + top: 0 + left: 0 + z-index: 3 + font-size: 12px + line-height: 8px + display: none + + span + display: block + position: absolute + top: 0 + left: 0 + background-color: #fff + color: #000 + @include dark-theme + background-color: #000 + color: #fff + + padding: 3px + + + .division + pointer-events: none + position: absolute + background: #fff + @include dark-theme + background-color: #000 + + + .value + position: absolute + font-size: 12px + + + .subdivision + position: absolute + + + #linkeditor-ruler-x + height: $rulers-size + width: 100% + + .info + height: $rulers-size + border-left: 1px dotted $rulers-color + @include dark-theme + border-color: $rulers-color-dark + + + .division, .subdivision + width: 0px + border-left: 1px solid $rulers-color + @include dark-theme + border-color: $rulers-color-dark + + + .division + height: $rulers-size + + .value + bottom: 0px + left: 3px + + + .subdivision + bottom: 0 + height: 2px + + &.middle + height: 5px + + + #linkeditor-ruler-y + width: $rulers-size + height: 100% + + .info + width: $rulers-size + border-bottom: 1px dotted $rulers-color + @include dark-theme + border-color: $rulers-color-dark + + + span + transform-origin: 0 0 + transform: rotate(270deg) + + + .division, .subdivision + height: 0px + border-bottom: 1px solid $rulers-color + @include dark-theme + border-color: $rulers-color-dark + + + .division + width: $rulers-size + + .value + text-align: center + max-width: $rulers-size + word-wrap: break-word + white-space: normal + line-height: 10px + letter-spacing: 30px + top: 3px + left: 3px + + + .subdivision + right: 0 + width: 2px + + &.middle + width: 5px diff --git a/resources/linkeditor-stable/style/inc/_toolbar.sass b/resources/linkeditor-stable/style/inc/_toolbar.sass new file mode 100644 index 000000000..e017ce1ec --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_toolbar.sass @@ -0,0 +1,125 @@ +#linkeditor-toolbar + background-color: #dbdddf + + @include dark-theme + background-color: #444 + + color: $toolbar-color + @include dark-theme + color: $toolbar-color-dark + + height: $toolbar-height + padding: 5px + + + nav + padding: 2px + display: inline-block + height: 26px + vertical-align: top + width: 33% + + &#linkeditor-toolbar-center + text-align: center + + &#linkeditor-toolbar-right + text-align: right + + div + display: inline-block + font-size: $font-size + vertical-align: top + + .separator + width: 0 + height: 26px + margin: 0 2px + border-left: 1px solid $toolbar-color + + div[data-icon] + position: relative + cursor: pointer + + form.importExcel + input + position: absolute + opacity: 0 + width: 100% + height: 100% + z-index: 1 + cursor: pointer + top: 0 + left: 0 + + #linkeditor-page-field + padding: 2px 10px + background-color: #fff + @include dark-theme + background-color: #000 + border-color: #333 + + border: 1px solid #ccc + border-radius: 5px + cursor: text + font-size: 13px + user-select: none + margin: 2px 8px + font-weight: 600 + + input + vertical-align: top + text-align: right + border: 0 + background-color: transparent + width: 20px + color: $toolbar-color + @include dark-theme + color: $toolbar-color-dark + + font-size: 13px + font-weight: 600 + position: relative + + &:hover, &:focus + outline: 0 + border: 0 + + [data-icon] + display: inline-block + vertical-align: top + height: 26px + min-width: 26px + padding: 3px + border-radius: 5px + margin: 0 3px + text-align: center + color: $toolbar-color + @include dark-theme + color: $toolbar-color-dark + + &.disabled + &:hover, &.hover, &.active + background-color: transparent + @include dark-theme + background-color: transparent + + color: $toolbar-color-disabled + @include dark-theme + color: $toolbar-color-disabled-dark + + &:hover, &.hover, &.active + background-color: #fff + @include dark-theme + background-color: #000 + + &.arrow + padding: 1px + + svg + height: 16px + + svg + position: relative + top: 1px + height: 18px + width: auto diff --git a/resources/linkeditor-stable/style/inc/_variables.sass b/resources/linkeditor-stable/style/inc/_variables.sass new file mode 100644 index 000000000..2246963c8 --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_variables.sass @@ -0,0 +1,18 @@ +$font: Montserrat, sans-serif +$font-size: 16px +$sidebar-icons-width: 46px +$sidebar-handle-width: 3px +$rulers-size: 16px +$ruler-margin: 2px + +$toolbar-height: 40px +$toolbar-color: #5d5d5d +$toolbar-color-dark: #bbb + +$toolbar-color-disabled: #bbb +$toolbar-color-disabled-dark: #666 + +$panel-background: #dcdcdc + +$color: #5d5d5d +$color-dark: #bbb diff --git a/resources/linkeditor-stable/style/inc/_versions.sass b/resources/linkeditor-stable/style/inc/_versions.sass new file mode 100644 index 000000000..8ca744134 --- /dev/null +++ b/resources/linkeditor-stable/style/inc/_versions.sass @@ -0,0 +1,77 @@ +#linkeditor-panel-versions + padding: 5px 10px + user-select: none + +#linkeditor-panel-versions-list + font-size: 12px + width: 100% + border-collapse: collapse + color: $color + + $icon-size: 18px + + @include dark-theme + color: $color-dark + + .row + padding: 5px 0 + vertical-align: top + border-bottom: 1px solid currentColor + + + >div + display: inline-block + vertical-align: top + + .date + font-weight: bold + .comments + font-style: italic + + .col1 + width: calc(100% - 125px) + + .col2 + width: 70px + + .col3 + width: 55px + vertical-align: middle + padding-top: 5px + a + width: $icon-size + height: $icon-size + margin-left: 7px + color: currentColor + cursor: pointer + + svg + width: $icon-size + height: $icon-size + + &.small + .row + position: relative + > div + display: block + .rulers, .links + display: inline + .links + &::after + content: ', ' + .col1, .col2 + width: calc(100% - 25px) + overflow: hidden + .col3 + position: absolute + top: 10px + right: 10px + width: $icon-size + padding: 0 + a + display: block + margin-bottom: 10px + + + + diff --git a/resources/linkeditor-stable/style/style.sass b/resources/linkeditor-stable/style/style.sass new file mode 100644 index 000000000..d43720f4e --- /dev/null +++ b/resources/linkeditor-stable/style/style.sass @@ -0,0 +1,198 @@ +@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap') +@import "inc/_variables" +@import "inc/_mixins" + +* + margin: 0 + padding: 0 + box-sizing: border-box + + &:focus + outline: 0 + + +body + font-family: "Montserrat", sans-serif + background-color: #ebecee + @include dark-theme + background-color: #333 + +img, .division, .info + user-select: none + user-drag: none + +body, #linkeditor, html + height: 100% + width: 100% + overflow: hidden + +#linkeditor + white-space: nowrap + font-size: 0 + overflow: hidden + + .when-selection-2, .when-selection-3 + display: inline-block + + &[data-selection-count="0"], &[data-selection-count="1"] + .when-selection-2, .when-selection-3 + display: none + + &[data-selection-count="2"] + .when-selection-3 + display: none + + .linkeditor-sidebar, #linkeditor-main + display: inline-block + height: 100% + vertical-align: top + text-align: left + + #linkeditor-main + &.grab + cursor: grab + + &.grabbing + cursor: grabbing + + &.polygon + cursor: crosshair + + .link:not(.pendingPolygonCreate) + pointer-events: none + + + #linkeditor-duplicatelink-overlay, #linkeditor-selectlink-overlay + display: none + position: absolute + z-index: 700 + top: $rulers-size + left: $rulers-size + height: calc(100% - $rulers-size) + max-height: calc(100% - $rulers-size) + width: calc(100% - $rulers-size) + max-width: calc(100% - $rulers-size) + + &.duplicate + #linkeditor-duplicatelink-overlay + display: block + cursor: copy + + &.selection + #linkeditor-selectlink-overlay + display: block + cursor: crosshair + + #linkeditor-selectlink-rect + border: 1px dashed #000000 + background-color: rgba(255, 255, 255, 0.25) + position: absolute + + #linkeditor-editor + position: relative + height: calc(100% - $toolbar-height) + width: 100% + overflow: hidden + + #linkeditor-zoom + width: 200% + height: 200% + max-width: 200% + max-height: 200% + min-height: 200% + min-width: 200% + transform-origin: 0 0 + + #linkeditor-canvas + background-color: #505050 + @include dark-theme + background-color: #222 + + position: relative + z-index: 1 + top: $rulers-size + left: $rulers-size + height: calc(100% - $rulers-size) + max-height: calc(100% - $rulers-size) + width: calc(100% - $rulers-size) + max-width: calc(100% - $rulers-size) + overflow: auto + + @include tinyscrollbars + + &.noscroll + overflow: hidden + + #linkeditor-fluidbook + transform-origin: 0 0 + position: absolute + top: 0 + left: 0 + + .linkeditor-page + position: absolute + top: 0px + left: 0px + background-color: rgba(255, 255, 255, 0.2) + @include dark-theme + background-color: rgba(0, 0, 0, 0.2) + + .contents + background-color: #fff + position: absolute + top: 0 + left: 0 + width: 100% + height: 100% + + img + display: block + position: absolute + top: 0 + left: 0 + width: 100% + height: 100% + z-index: 1 + + &.texts + z-index: 2 + + &.white-overlay + &:after + content: '' + position: absolute + top: 0 + left: 0 + width: 100% + height: 100% + z-index: 3 + pointer-events: none + background-color: rgba(255, 255, 255, 0.75) + + &.single + #linkeditor-page-right + display: none + +#linkeditor-preload, #linkeditor-clipboard + display: none + +#loader + position: absolute + top: 0 + left: 0 + cursor: wait + width: 100% + height: 100% + z-index: 20000000 + display: none + +@import "inc/_panels" +@import "inc/_toolbar" +@import "inc/_rulers" +@import "inc/_links" +@import "inc/_form" +@import "inc/_versions" +@import "inc/_popup" +@import "inc/_contextmenu" +@import "inc/_layers" + diff --git a/resources/linkeditor-stable/webpack.mix.js b/resources/linkeditor-stable/webpack.mix.js new file mode 100644 index 000000000..cd9aaae1c --- /dev/null +++ b/resources/linkeditor-stable/webpack.mix.js @@ -0,0 +1,3 @@ +const mix = require("laravel-mix"); +mix.setPublicPath('public/packages/linkeditor-stable').js('resources/linkeditor-stable/js/linkeditor.js', 'js') + .sass('resources/linkeditor/style/style.sass', 'css').options({processCssUrls: false}).version(); diff --git a/resources/views/vendor/backpack/crud/buttons/fluidbook_publication/edit.blade.php b/resources/views/vendor/backpack/crud/buttons/fluidbook_publication/edit.blade.php index a530bd7ca..60903569b 100644 --- a/resources/views/vendor/backpack/crud/buttons/fluidbook_publication/edit.blade.php +++ b/resources/views/vendor/backpack/crud/buttons/fluidbook_publication/edit.blade.php @@ -17,6 +17,12 @@ 'url'=>$crud->route.'/$id/edit/$action', 'target'=>'_blank' ]; + $actions ['links-beta']= + [ + 'label'=>__('Modifier les liens').' ('.__('Version de dev').')', + 'url'=>$crud->route.'/$id/edit/$action', + 'target'=>'_blank' + ]; } @endphp @if($entry->allowsEdit())