diff --git a/addons/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js b/addons/web_editor/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js similarity index 100% rename from addons/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js rename to addons/web_editor/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js diff --git a/addons/web_editor/static/src/js/ace.js b/addons/web_editor/static/src/js/ace.js new file mode 100644 index 0000000000000000000000000000000000000000..cd20815f49c561b497e29488c692b74d89bbcc2f --- /dev/null +++ b/addons/web_editor/static/src/js/ace.js @@ -0,0 +1,320 @@ +odoo.define('web_editor.ace', function (require) { +'use strict'; + +var ajax = require('web.ajax'); +var Class = require('web.Class'); +var core = require('web.core'); +var Widget = require('web.Widget'); +var base = require('web_editor.base'); +var local_storage = require('web.local_storage'); + +var qweb = core.qweb; +var _t = core._t; + +ajax.loadXML('/web_editor/static/src/xml/ace.xml', qweb); + +var XmlDocument = Class.extend({ + init: function (text) { + this.xml = text; + }, + isWellFormed: function () { + var error; + if (document.implementation.createDocument) { + // use try catch for ie + try { + var dom = new DOMParser().parseFromString(this.xml, "text/xml"); + error = dom.getElementsByTagName("parsererror"); + return error.length === 0 || $(error).text(); + } catch (e) {} + } + if (window.ActiveXObject) { + // IE + var msDom = new ActiveXObject("Microsoft.XMLDOM"); + msDom.async = false; + msDom.loadXML(this.xml); + return !msDom.parseError.errorCode || msDom.parseError.reason + "\nline " + msDom.parseError.line; + } + return true; + }, + format: function () { + return vkbeautify.xml(this.xml, 4); + }, +}); + +var ViewOption = Widget.extend({ + template: 'web_editor.ace_view_option', + init: function (parent, options) { + this.view_id = options.id; + this.view_name = options.name; + + var indent = _.str.repeat("- ", options.level); + this.view_name = _.str.sprintf("%s%s", indent, options.name); + this._super(parent); + }, +}); + +var ViewEditor = Widget.extend({ + resizing: false, + refX: 0, + minWidth: 40, + template: 'web_editor.ace_view_editor', + events: { + 'change #ace-view-list': 'displaySelectedView', + 'click .js_include_bundles': 'loadTemplates', + 'click button[data-action=save]': 'saveViews', + 'click button[data-action=format]': 'formatXml', + 'click button[data-action=close]': 'close', + }, + init: function (parent) { + this.buffers = {}; + this.views = {}; + this._super(parent); + }, + start: function () { + var self = this; + self.aceEditor = ace.edit(self.$('#ace-view-editor')[0]); + self.aceEditor.setTheme("ace/theme/monokai"); + self.loadTemplates(); + + var $editor = self.$('.ace_editor'); + function resizeEditor (target) { + var width = Math.min(document.body.clientWidth, Math.max(parseInt(target, 10), self.minWidth)); + $editor.width(width); + self.aceEditor.resize(); + self.$el.width(width); + } + function storeEditorWidth() { + local_storage.setItem('ace_editor_width', self.$el.width()); + } + function readEditorWidth() { + var width = local_storage.getItem('ace_editor_width'); + return parseInt(width || 720, 10); + } + function startResizing (e) { + self.refX = e.pageX; + self.resizing = true; + } + function stopResizing () { + self.resizing = false; + } + function updateWidth (e) { + if (self.resizing) { + var offset = e.pageX - self.refX; + var width = self.$el.width() - offset; + self.refX = e.pageX; + resizeEditor(width); + storeEditorWidth(); + } + } + document.body.addEventListener('mouseup', stopResizing, true); + self.$('.ace_gutter').mouseup(stopResizing).mousedown(startResizing).click(stopResizing); + $(document).mousemove(updateWidth); + $('button[data-action=edit]').click(function () { + self.close(); + }); + resizeEditor(readEditorWidth()); + }, + + // The 4 following methods are meant to be extended depending on the context + open: function () { + this.$el.removeClass('oe_ace_closed').addClass('oe_ace_open'); + }, + close: function () { + this.$el.removeClass('oe_ace_open').addClass('oe_ace_closed'); + }, + reloadPage: function () {}, + loadTemplates: function () {}, + + loadViews: function (views) { + var $viewList = this.$('#ace-view-list').empty(); + _(this.buildViewGraph(views)).each(function (view) { + if (!view.id) { return; } + + this.views[view.id] = view; + new ViewOption(this, view).appendTo($viewList); + this.loadView(view.id); + }.bind(this)); + }, + buildViewGraph: function (views) { + var activeViews = _.uniq(_.filter(views, function (view) { + return view.active; + }), false, function (view) { + return view.id; + }); + var index = {}; + var roots = []; + _.each(activeViews, function (view) { + index[view.id] = view; + view.children = []; + }); + _.each(index, function (view) { + var parentId = view.inherit_id; + if (parentId && index[parentId]) { + index[parentId].children.push(view); + } else { + roots.push(view); + } + }); + var result = []; + function visit (node, level) { + node.level = level; + result.push(node); + _.each(node.children, function (child) { + visit(child, level + 1); + }); + } + _.each(roots, function (node) { + visit(node, 0); + }); + return result; + }, + loadView: function (id) { + var viewId = parseInt(id, 10); + var self = this; + ajax.jsonRpc('/web/dataset/call', 'call', { + model: 'ir.ui.view', + method: 'read', + args: [[viewId], ['arch'], _.extend(base.get_context(), {'lang': null})], + }).then(function (result) { + var editingSession = self.buffers[viewId] = new ace.EditSession(result[0].arch); + editingSession.setMode("ace/mode/xml"); + editingSession.setUndoManager(new ace.UndoManager()); + editingSession.setUseWorker(false); + editingSession.on("change", function () { + setTimeout(function () { + var $option = self.$('#ace-view-list').find('[value='+viewId+']'); + var bufferName = $option.text(); + var dirtyMarker = " (unsaved changes)"; + var isDirty = editingSession.getUndoManager().hasUndo(); + if (isDirty && bufferName.indexOf(dirtyMarker) < 0) { + $option.text(bufferName + dirtyMarker); + } else if (!isDirty && bufferName.indexOf(dirtyMarker) > 0) { + $option.text(bufferName.substring(0, bufferName.indexOf(dirtyMarker))); + } + }, 1); + }); + if (viewId === self.selectedViewId()) { + self.displayView.call(self, viewId); + } + }); + }, + selectedViewId: function () { + return parseInt(this.$('#ace-view-list').val(), 10); + }, + displayView: function (id) { + var viewId = parseInt(id, 10); + var editingSession = this.buffers[viewId]; + if (editingSession) { + this.aceEditor.setSession(editingSession); + this.$('#ace-view-id').text(_.str.sprintf( + _t("Template ID: %s"), + this.views[viewId].xml_id)); + } + }, + displaySelectedView: function () { + this.displayView(this.selectedViewId()); + this.updateHash(); + }, + formatXml: function () { + var xml = new XmlDocument(this.aceEditor.getValue()); + this.aceEditor.setValue(xml.format()); + }, + saveViews: function () { + var self = this; + var toSave = _.filter(_.map(self.buffers, function (editingSession, viewId) { + return { + id: parseInt(viewId, 10), + isDirty: editingSession.getUndoManager().hasUndo(), + text: editingSession.getValue(), + }; + }), function (session) { + return session.isDirty; + }); + this.clearError(); + var requests = _.map(toSave, function (session) { + return self.saveView(session); + }); + $.when.apply($, requests).then(function () { + self.reloadPage.call(self); + }).fail(function (source, session, error) { + self.displayError.call(self, source, session, error); + }); + }, + saveView: function (session) { + var xml = new XmlDocument(session.text); + var isWellFormed = xml.isWellFormed(); + var def = $.Deferred(); + if (isWellFormed === true) { + ajax.jsonRpc('/web/dataset/call', 'call', { + model: 'ir.ui.view', + method: 'write', + args: [[session.id], { 'arch': xml.xml }, _.extend(base.get_context(), {'lang': null})], + }).then(function () { + def.resolve(); + }).fail(function (source, error) { + def.reject("server", session, error); + }); + } else { + def.reject(null, session, isWellFormed); + } + return def; + }, + clearError: function () { + this.$(".ace_layer.ace_text-layer .ace_line").css("background", ""); + }, + displayError: function (source, session, error) { + var self = this; + var line, test; + // format error message + var message = _.isString(error) ? error + : (error && error.data && error.data.arguments && error.data.arguments[0] === "Access Denied") ? "Access denied: please sign in" + : (error && error.data && error.data.message) ? error.data.message + : (error && error.message) ? error.message + : "Unexpected error"; + if (source == "server") { + message = eval(message.replace(/^\(/g, '([') + .replace(/\)$/g, '])') + .replace(/u'/g, "'") + .replace(/<([^>]+)>/g, '<b style="color:#661100;"><\$1></b>'))[1]; + line = -1; + } else { + line = message.match(/line ([0-9]+)/i); + line = line ? parseInt(line[1],10) : -1; + test = new RegExp("^\\s*"+line+"\\s*$"); + } + + function gotoline() { + self.aceEditor.gotoLine(line); + setTimeout(function () { + var $lines = self.$(".ace_editor .ace_gutter .ace_gutter-cell"); + var index = $lines.filter(function () { + return test.test($(this).text()); + }).index(); + if (index>0) { + self.$(".ace_layer.ace_text-layer .ace_line:eq(" + index + ")").css("background", "#661100"); + } + },100); + } + function onchangeSession () { + self.aceEditor.off('changeSession', onchangeSession); + gotoline(); + } + + var $list = this.$("#ace-view-list"); + if (+$list.val() == session.id) { + if (line>-1) gotoline(); + } else { + if (line) self.aceEditor.on('changeSession', onchangeSession); + this.$("#ace-view-list").val(session.id).change(); + } + return session.text.match(/\s+name=['"]([^'"]+)['"]/i)[1], "<b>Malformed XML document</b>:<br/>" + message; + }, +}); + +return { + XmlDocument: XmlDocument, + ViewOption: ViewOption, + ViewEditor: ViewEditor, +}; + +}); diff --git a/addons/web_editor/static/src/xml/ace.xml b/addons/web_editor/static/src/xml/ace.xml new file mode 100644 index 0000000000000000000000000000000000000000..2f18f60731fc6968e0adbc269912043e9a144d14 --- /dev/null +++ b/addons/web_editor/static/src/xml/ace.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates> + +<t t-name="web_editor.ace_view_editor"> + <div class="o_ace_view_editor oe_ace_closed"> + <div class="form-inline o_ace_view_editor_title"> + <select id="ace-view-list" class="form-control oe_view_list"> + <!-- filled in JS --> + </select> + <label class="oe_include_bundles"> + <input type="checkbox" class="js_include_bundles"/> Include Asset Bundles + </label> + <button data-action="save" type="submit" class="btn btn-success">Save</button> + <button data-action="format" type="button" class="btn btn-warning">Format</button> + <button data-action="close" type="button" class="btn btn-info">Close</button> + </div> + <div id="ace-view-id"/> + <div id="ace-view-editor"/> + </div> +</t> + +<t t-name="web_editor.ace_view_option"> + <option t-att-value="widget.view_id"><t t-esc="widget.view_name"/></option> +</t> + +</templates> diff --git a/addons/web_editor/views/editor.xml b/addons/web_editor/views/editor.xml index 056b2df4169639e150cc2963667e5fd7f82d8499..6c7f23f882241367d791614f94ee483832419c4f 100644 --- a/addons/web_editor/views/editor.xml +++ b/addons/web_editor/views/editor.xml @@ -60,6 +60,8 @@ <template id="assets_common" inherit_id="web.assets_common" name="web_editor base assets_common"> <xpath expr="script[last()]" position="after"> + <script type="text/javascript" src="/web_editor/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script> + <script type="text/javascript" src="/web_editor/static/src/js/ace.js" /> <script type="text/javascript" src="/web_editor/static/src/js/base.js" /> <script type="text/javascript" src="/web_editor/static/src/js/snippets.animation.js" /> </xpath> diff --git a/addons/website/static/src/js/website.ace.js b/addons/website/static/src/js/website.ace.js index 31717241a9d0b65c7a1827139f79306d9eaf6169..a89896939b7b03e8dfa9abe3f36bb95deb8e8bc1 100644 --- a/addons/website/static/src/js/website.ace.js +++ b/addons/website/static/src/js/website.ace.js @@ -2,20 +2,13 @@ odoo.define('website.ace', function (require) { 'use strict'; var ajax = require('web.ajax'); -var Class = require('web.Class'); -var core = require('web.core'); var Widget = require('web.Widget'); -var base = require('web_editor.base'); var website = require('website.website'); -var local_storage = require('web.local_storage'); -var _t = core._t; -var qweb = core.qweb; +var AceCommon = require('web_editor.ace'); var hash = "#advanced-view-editor"; -ajax.loadXML('/website/static/src/xml/website.ace.xml', qweb); - var Ace = Widget.extend({ events: { 'click a[data-action=ace]': 'launchAce', @@ -44,107 +37,7 @@ var Ace = Widget.extend({ }, }); -var XmlDocument = Class.extend({ - init: function (text) { - this.xml = text; - }, - isWellFormed: function () { - var error; - if (document.implementation.createDocument) { - // use try catch for ie - try { - var dom = new DOMParser().parseFromString(this.xml, "text/xml"); - error = dom.getElementsByTagName("parsererror"); - return error.length === 0 || $(error).text(); - } catch (e) {} - } - if (window.ActiveXObject) { - // IE - var msDom = new ActiveXObject("Microsoft.XMLDOM"); - msDom.async = false; - msDom.loadXML(this.xml); - return !msDom.parseError.errorCode || msDom.parseError.reason + "\nline " + msDom.parseError.line; - } - return true; - }, - format: function () { - return vkbeautify.xml(this.xml, 4); - }, -}); - -var ViewOption = Widget.extend({ - template: 'website.ace_view_option', - init: function (parent, options) { - this.view_id = options.id; - this.view_name = options.name; - - var indent = _.str.repeat("- ", options.level); - this.view_name = _.str.sprintf("%s%s", indent, options.name); - this._super(parent); - }, -}); - -var ViewEditor = Widget.extend({ - resizing: false, - refX: 0, - minWidth: 40, - template: 'website.ace_view_editor', - events: { - 'change #ace-view-list': 'displaySelectedView', - 'click .js_include_bundles': 'loadTemplates', - 'click button[data-action=save]': 'saveViews', - 'click button[data-action=format]': 'formatXml', - 'click button[data-action=close]': 'close', - }, - init: function (parent) { - this.buffers = {}; - this.views = {}; - this._super(parent); - }, - start: function () { - var self = this; - self.aceEditor = ace.edit(self.$('#ace-view-editor')[0]); - self.aceEditor.setTheme("ace/theme/monokai"); - self.loadTemplates(); - - var $editor = self.$('.ace_editor'); - function resizeEditor (target) { - var width = Math.min(document.body.clientWidth, Math.max(parseInt(target, 10), self.minWidth)); - $editor.width(width); - self.aceEditor.resize(); - self.$el.width(width); - } - function storeEditorWidth() { - local_storage.setItem('ace_editor_width', self.$el.width()); - } - function readEditorWidth() { - var width = local_storage.getItem('ace_editor_width'); - return parseInt(width || 720, 10); - } - function startResizing (e) { - self.refX = e.pageX; - self.resizing = true; - } - function stopResizing () { - self.resizing = false; - } - function updateWidth (e) { - if (self.resizing) { - var offset = e.pageX - self.refX; - var width = self.$el.width() - offset; - self.refX = e.pageX; - resizeEditor(width); - storeEditorWidth(); - } - } - document.body.addEventListener('mouseup', stopResizing, true); - self.$('.ace_gutter').mouseup(stopResizing).mousedown(startResizing).click(stopResizing); - $(document).mousemove(updateWidth); - $('button[data-action=edit]').click(function () { - self.close(); - }); - resizeEditor(readEditorWidth()); - }, +var ViewEditor = AceCommon.ViewEditor.extend({ loadTemplates: function () { var self = this; var args = { @@ -171,139 +64,9 @@ var ViewEditor = Widget.extend({ } }); }, - loadViews: function (views) { - var $viewList = this.$('#ace-view-list').empty(); - _(this.buildViewGraph(views)).each(function (view) { - if (!view.id) { return; } - - this.views[view.id] = view; - new ViewOption(this, view).appendTo($viewList); - this.loadView(view.id); - }.bind(this)); - }, - buildViewGraph: function (views) { - var activeViews = _.uniq(_.filter(views, function (view) { - return view.active; - }), false, function (view) { - return view.id; - }); - var index = {}; - var roots = []; - _.each(activeViews, function (view) { - index[view.id] = view; - view.children = []; - }); - _.each(index, function (view) { - var parentId = view.inherit_id; - if (parentId && index[parentId]) { - index[parentId].children.push(view); - } else { - roots.push(view); - } - }); - var result = []; - function visit (node, level) { - node.level = level; - result.push(node); - _.each(node.children, function (child) { - visit(child, level + 1); - }); - } - _.each(roots, function (node) { - visit(node, 0); - }); - return result; - }, - loadView: function (id) { - var viewId = parseInt(id, 10); - var self = this; - ajax.jsonRpc('/web/dataset/call', 'call', { - model: 'ir.ui.view', - method: 'read', - args: [[viewId], ['arch'], _.extend(base.get_context(), {'lang': null})], - }).then(function (result) { - var editingSession = self.buffers[viewId] = new ace.EditSession(result[0].arch); - editingSession.setMode("ace/mode/xml"); - editingSession.setUndoManager(new ace.UndoManager()); - editingSession.setUseWorker(false); - editingSession.on("change", function () { - setTimeout(function () { - var $option = self.$('#ace-view-list').find('[value='+viewId+']'); - var bufferName = $option.text(); - var dirtyMarker = " (unsaved changes)"; - var isDirty = editingSession.getUndoManager().hasUndo(); - if (isDirty && bufferName.indexOf(dirtyMarker) < 0) { - $option.text(bufferName + dirtyMarker); - } else if (!isDirty && bufferName.indexOf(dirtyMarker) > 0) { - $option.text(bufferName.substring(0, bufferName.indexOf(dirtyMarker))); - } - }, 1); - }); - if (viewId === self.selectedViewId()) { - self.displayView.call(self, viewId); - } - }); - }, - selectedViewId: function () { - return parseInt(this.$('#ace-view-list').val(), 10); - }, - displayView: function (id) { - var viewId = parseInt(id, 10); - var editingSession = this.buffers[viewId]; - if (editingSession) { - this.aceEditor.setSession(editingSession); - this.$('#ace-view-id').text(_.str.sprintf( - _t("Template ID: %s"), - this.views[viewId].xml_id)); - } - }, - displaySelectedView: function () { - this.displayView(this.selectedViewId()); - this.updateHash(); - }, - formatXml: function () { - var xml = new XmlDocument(this.aceEditor.getValue()); - this.aceEditor.setValue(xml.format()); - }, - saveViews: function () { - var self = this; - var toSave = _.filter(_.map(self.buffers, function (editingSession, viewId) { - return { - id: parseInt(viewId, 10), - isDirty: editingSession.getUndoManager().hasUndo(), - text: editingSession.getValue(), - }; - }), function (session) { - return session.isDirty; - }); - this.clearError(); - var requests = _.map(toSave, function (session) { - return self.saveView(session); - }); - $.when.apply($, requests).then(function () { - self.reloadPage.call(self); - }).fail(function (source, session, error) { - self.displayError.call(self, source, session, error); - }); - }, - saveView: function (session) { - var xml = new XmlDocument(session.text); - var isWellFormed = xml.isWellFormed(); - var def = $.Deferred(); - if (isWellFormed === true) { - ajax.jsonRpc('/web/dataset/call', 'call', { - model: 'ir.ui.view', - method: 'write', - args: [[session.id], { 'arch': xml.xml }, _.extend(base.get_context(), {'lang': null})], - }).then(function () { - def.resolve(); - }).fail(function (source, error) { - def.reject("server", session, error); - }); - } else { - def.reject(null, session, isWellFormed); - } - return def; + displayError: function () { + var error_message = this._super.apply(this, arguments); + website.error(error_message); }, updateHash: function () { window.location.hash = hash + "?view=" + this.selectedViewId(); @@ -312,60 +75,6 @@ var ViewEditor = Widget.extend({ this.updateHash(); window.location.reload(); }, - clearError: function () { - this.$(".ace_layer.ace_text-layer .ace_line").css("background", ""); - }, - displayError: function (source, session, error) { - var self = this; - var line, test; - // format error message - var message = _.isString(error) ? error - : (error && error.data && error.data.arguments && error.data.arguments[0] === "Access Denied") ? "Access denied: please sign in" - : (error && error.data && error.data.message) ? error.data.message - : (error && error.message) ? error.message - : "Unexpected error"; - if (source == "server") { - message = eval(message.replace(/^\(/g, '([') - .replace(/\)$/g, '])') - .replace(/u'/g, "'") - .replace(/<([^>]+)>/g, '<b style="color:#661100;"><\$1></b>'))[1]; - line = -1; - } else { - line = message.match(/line ([0-9]+)/i); - line = line ? parseInt(line[1],10) : -1; - test = new RegExp("^\\s*"+line+"\\s*$"); - } - - function gotoline() { - self.aceEditor.gotoLine(line); - setTimeout(function () { - var $lines = self.$(".ace_editor .ace_gutter .ace_gutter-cell"); - var index = $lines.filter(function () { - return test.test($(this).text()); - }).index(); - if (index>0) { - self.$(".ace_layer.ace_text-layer .ace_line:eq(" + index + ")").css("background", "#661100"); - } - },100); - } - function onchangeSession () { - self.aceEditor.off('changeSession', onchangeSession); - gotoline(); - } - - var $list = this.$("#ace-view-list"); - if (+$list.val() == session.id) { - if (line>-1) gotoline(); - } else { - if (line) self.aceEditor.on('changeSession', onchangeSession); - this.$("#ace-view-list").val(session.id).change(); - } - - website.error(session.text.match(/\s+name=['"]([^'"]+)['"]/i)[1], "<b>Malformed XML document</b>:<br/>" + message); - }, - open: function () { - this.$el.removeClass('oe_ace_closed').addClass('oe_ace_open'); - }, close: function () { window.location.hash = ""; this.$el.removeClass('oe_ace_open').addClass('oe_ace_closed'); diff --git a/addons/website/static/src/xml/website.ace.xml b/addons/website/static/src/xml/website.ace.xml deleted file mode 100644 index 9ed0408c3dc05511d56d6eff4a52ce1be403ca6d..0000000000000000000000000000000000000000 --- a/addons/website/static/src/xml/website.ace.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<templates id="template" xml:space="preserve"> - <t t-name="website.ace_view_editor"> - <div class="o_ace_view_editor oe_ace_closed"> - <div class="form-inline o_ace_view_editor_title"> - <select id="ace-view-list" class="form-control oe_view_list"> - <!-- filled in JS --> - </select> - <label class="oe_include_bundles"> - <input type="checkbox" class="js_include_bundles"/> Include Asset Bundles - </label> - <button data-action="save" type="submit" class="btn btn-success">Save</button> - <button data-action="format" type="button" class="btn btn-warning">Format</button> - <button data-action="close" type="button" class="btn btn-info">Close</button> - </div> - <div id="ace-view-id"/> - <div id="ace-view-editor"/> - </div> - </t> - <t t-name="website.ace_view_option"> - <option t-att-value="widget.view_id"><t t-esc="widget.view_name"/></option> - </t> -</templates> diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index b617af701369e595d330f31ec85486e57b617af3..4996db250225b82f23bc9e557afef57450b1e09c 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -33,7 +33,6 @@ <script type="text/javascript" src="/website/static/src/js/website.editor.js"></script> <script type="text/javascript" src="/website/static/lib/jQuery.transfo.js"></script> - <script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script> <script type="text/javascript" src="/website/static/src/js/website.mobile.js"></script> <script type="text/javascript" src="/website/static/src/js/website.seo.js"></script> <script type="text/javascript" src="/website/static/src/js/website.tour.js"></script>