// __('!!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
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');
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;
$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)
$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
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"]);
--- /dev/null
+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;
--- /dev/null
+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('<fluidbooklinks>') >= 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(['<fluidbooklinks>' + this.content + '</fluidbooklinks>'], {type});
+ const data = [new ClipboardItem({[type]: blob})];
+
+ navigator.clipboard.write(data).then(
+ () => {
+ $this.checkEmpty();
+ /* success */
+ },
+ () => {
+ /* failure */
+ }
+ );
+ }
+ },
+}
+
+export default LinkeditorClipboard;
--- /dev/null
+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;
--- /dev/null
+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);
--- /dev/null
+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 = '<em>' + TRANSLATIONS.empty + '</em>';
+ }
+ var l = '<div class="layer" data-locked="' + ($this.linkeditor.links.locks.isLocked($(this).attr('fb-uid')) ? '1' : '0') + '">';
+ l += '<label class="layer" fb-type="' + type + '">';
+ l += '<input name="' + $(this).attr('fb-uid') + '" type="checkbox"> ';
+ l += dest;
+ l += '<span data-tooltip="' + TRANSLATIONS.click_to_copy_id + '" data-uid="' + $(this).attr('fb-uid') + '" class="uid">#' + $(this).attr('fb-uid') + '</span>';
+ l += '</label>';
+ l += '<a href="#" class="lock" data-icon="lock" data-tooltip="' + TRANSLATIONS['lock'] + '"></a>';
+ l += '</div>';
+ 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('<h3>' + TRANSLATIONS.level + ' #' + v.level + '</h3>');
+ }
+ $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;
--- /dev/null
+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 + ' <kbd>Ctrl+A</kbd>', 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 + ' <kbd>Ctrl+L</kbd>',
+ callback: function () {
+ $this.openImageLink();
+ }
+ };
+ }
+ }
+ if (hasSelection) {
+ res.items.sep_lock = '---------';
+ res.items.lock = {
+ isHtmlName: true, name: TRANSLATIONS.lock + ' <kbd>Ctrl+O</kbd>', callback: function () {
+ $this.locks.lockSelection();
+ },
+ };
+ }
+ if (hasSelection || hasClipboard) {
+ res.items.sep_clipboard = '---------';
+ if (hasSelection) {
+ res.items.copy_to_clipboad = {
+ isHtmlName: true, name: TRANSLATIONS.copy + ' <kbd>Ctrl+C</kbd>', callback: function () {
+ $this.copy();
+ },
+ };
+ res.items.cut_to_clipboad = {
+ isHtmlName: true, name: TRANSLATIONS.cut + ' <kbd>Ctrl+X</kbd>', 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 + ' <kbd>Ctrl+V</kbd>',
+ 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) + ' <kbd>Del</kbd>',
+ 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 = $('<div />').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 = $('<div />');
+ $.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('<div class="corners"><div class="nw"></div><div class="n"></div><div class="ne"></div><div class="e"></div><div class="se"></div><div class="s"></div><div class="sw"></div><div class="w"></div></div>')
+ }
+ $(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 = $('<div class="link" fb-ref="editor" fb-update="1"></div>');
+ $(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 = '<svg preserveAspectRatio="none" viewBox="0 0 ' + w + ' ' + h + '" width="' + w + '" height="' + h + '" style="width: 100%;height:100%;"><polygon points="';
+ let corners = $('<div class="corners"></div>');
+ 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('<div class="poly" data-point="' + k + '" style="left:calc(' + cx + '% - 4px);top:calc(' + cy + '% - 4px);"></div>');
+
+ 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"></svg>';
+ $(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;
--- /dev/null
+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;
--- /dev/null
+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 = $('<div class="preload" data-page="i"></div>');
+ $("#linkeditor-preload").append(c);
+ $this._loadPage(i, c, true);
+ }, j * 1500));
+ j++;
+ }
+ },
+
+ _loadPage: function (p, container) {
+ var imageFormat = FLUIDBOOK_DATA.settings.imageFormat;
+ var c = '<div class="contents">';
+ if (this.linkeditor.utils.isSpecialPage(p)) {
+ let data = this.linkeditor.utils.getSpecialPageAssetData(p);
+ c += '<img draggable="false" width="' + data.dim[0] + '" height="' + data.dim[1] + '" class="images" src="' + data.url + '" />';
+ } else {
+ if (this.pagesSource === 'pages') {
+ if (this.rasterizePages.indexOf(p) >= 0) {
+ c += '<img draggable="false" src="raster_' + p + '.' + imageFormat + this.noCache + '" />';
+ } else if (this.vectorPages.indexOf(p) >= 0) {
+ c += '<img draggable="false" src="vector_' + p + '.svg' + this.noCache + '" />';
+ } else {
+ c += '<img draggable="false" class="images" src="images_' + p + '.' + imageFormat + this.noCache + '" />';
+ c += '<img draggable="false" class="texts" src="texts_' + p + '.svg' + this.noCache + '" />';
+ }
+ } else if (this.pagesSource === 'thumbnails') {
+ c += '<img draggable="false" src="thumbspdf_' + p + '.' + imageFormat + this.noCache + '" />';
+ }
+ }
+ c += '</div>';
+ $(container).html(c);
+ },
+}
+export default LinkeditorLoader;
--- /dev/null
+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('<div id="linkeditor-panel-' + panel + '"></div>');
+ }
+ $('#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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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 = '<div class="info"><span>1234.12</span></div>';
+ 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 += '<div class="division" style="top:' + ystart + 'px;height:' + divisionSize + 'px;"><div class="value">' + Math.abs(v) + '</div>';
+ for (let j = 1; j <= 9; j++) {
+ yruler += '<div class="subdivision ' + (j === 5 ? ' middle' : '') + '" style="top:' + ((j * divisionSize) / 10) + 'px;"></div>';
+ }
+ yruler += '</div>';
+ }
+ $("#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 = '<div class="info"><span></span></div>';
+
+ 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 = '<div class="division" style="left:' + xstart + 'px;width:' + divisionSize + 'px;"><div class="value">' + Math.abs(v) + '</div>';
+ for (let i = 1; i <= 9; i++) {
+ let cls = '';
+ if (i === 5) {
+ cls += ' middle';
+ }
+ res += '<div class="subdivision ' + cls + '" style="left:' + ((i * divisionSize) / 10) + 'px;"></div>';
+ }
+ res += '</div>';
+ 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 = $('<div class="ruler" data-uid="' + uid + '" fb-update="1" fb-ref="editor" data-axis="' + axis + '"></div>');
+ 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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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 = '<div class="row">';
+ item += '<div class="col1">';
+ item += '<div class="date">' + version.date + '</div>';
+ item += '<div class="name">' + version.name + '</div>';
+ item += '<div class="comments">' + version.comments + '</div>';
+ item += '</div>';
+ item += '<div class="col2">';
+ item += '<div class="links"><b>' + version.links + '</b> links</div>';
+ item += '<div class="rulers"><b>' + version.rulers + '</b> rulers</div>';
+ item += '</div>'
+ item += '<div class="col3">';
+ item += '<div class="actions"><a nohref data-action="versions.restoreVersion" data-action-args="' + actionArgs + '" data-icon="wayback-machine" data-tooltip="' + TRANSLATIONS.restore_version_tooltip + '" draggable="false"></a>' +
+ '<a download="links_' + FLUIDBOOK_DATA.id + '_' + version.timestamp + '.xlsx" href="/fluidbook-publication/' + FLUIDBOOK_DATA.id + '/edit/links/versions/export/' + version.timestamp + '" data-icon="export-links" data-tooltip="' + TRANSLATIONS.export_version_tooltip + '" draggable="false"></a></div>';
+ item += '</div>'
+ item += '</div>'
+ 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;
--- /dev/null
+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;
--- /dev/null
+.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
--- /dev/null
+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
--- /dev/null
+#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
--- /dev/null
+@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
--- /dev/null
+@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
--- /dev/null
+.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
--- /dev/null
+#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
--- /dev/null
+@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
--- /dev/null
+#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
--- /dev/null
+$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
--- /dev/null
+#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
+
+
+
+
--- /dev/null
+@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"
+
--- /dev/null
+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();
'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())