From 4d911c7187d0196e9d5791b33d5ed9daa052cffe Mon Sep 17 00:00:00 2001 From: Erik Loyer Date: Sun, 19 Feb 2023 09:00:33 -0800 Subject: [PATCH 01/31] Refactor to add ScalarUser class and capabilities method to scalarapi --- .../melons/cantaloupe/js/scalarhelp.jquery.js | 2 +- .../views/widgets/api/scalarapi.js | 88 ++++++++++++++++--- .../views/widgets/nav/jquery.scalarnav.js | 6 +- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/system/application/views/melons/cantaloupe/js/scalarhelp.jquery.js b/system/application/views/melons/cantaloupe/js/scalarhelp.jquery.js index 3ca3f04ba..577ffa104 100755 --- a/system/application/views/melons/cantaloupe/js/scalarhelp.jquery.js +++ b/system/application/views/melons/cantaloupe/js/scalarhelp.jquery.js @@ -43,7 +43,7 @@ var me = this; - var canEdit = ( !isMobile && ((scalarapi.model.user_level == "scalar:Author") || (scalarapi.model.user_level == "scalar:Commentator") || (scalarapi.model.user_level == "scalar:Reviewer"))); + var canEdit = ( !isMobile && ((scalarapi.model.getUser().user_level == "scalar:Author") || (scalarapi.model.getUser().user_level == "scalar:Commentator") || (scalarapi.model.getUser().user_level == "scalar:Reviewer"))); var content = $('
'); content.append('

The header bar at the top of the screen gives you access to utilities for navigating and editing (if you’re logged in and have editing privileges). If the header bar is currently hidden, scroll towards the top of the page to make it appear. Here’s a quick reference guide to the header bar icons:

'); diff --git a/system/application/views/widgets/api/scalarapi.js b/system/application/views/widgets/api/scalarapi.js index 4bf387790..c1ca291bb 100755 --- a/system/application/views/widgets/api/scalarapi.js +++ b/system/application/views/widgets/api/scalarapi.js @@ -17,6 +17,15 @@ * permissions and limitations under the License. */ +const ScalarRole = { + Unknown: 'unknown', + Reader: 'reader', + Commentator: 'commentator', + Reviewer: 'reviewer', + Author: 'author', + Editor: 'editor', +} + var scalarapi = new ScalarAPI(); function is_array(input){ @@ -35,11 +44,7 @@ function ScalarAPI() { var me = this; - this.model = new ScalarModel({ - parent_uri: $('link#parent').attr('href'), - logged_in: $('link#logged_in').attr('href'), - user_level: $('link#user_level').attr('href') - }); + this.model = new ScalarModel(); /** * Browser detection script from http://www.quirksmode.org/js/detect.html @@ -726,6 +731,7 @@ ScalarAPI.prototype.browserDetect = null; ScalarAPI.prototype.nodeExistsCallback = null; ScalarAPI.prototype.untitledNodeString = null; + /** * Returns the specified uri without any extraneous trailing content (getVars, etc.) * @@ -1224,6 +1230,23 @@ ScalarAPI.prototype.toNS = function(uri) { return false; }; +ScalarAPI.prototype.getCapabilitiesFromURL = function(url) { + let capabilities = { + canAnnotate: true, + canEdit: true, + canDelete: true, + unrestricted: true, + } + let suffix = this.getFileExtension(url); + if(['versions','history','annotation_editor','manage_lenses'].indexOf(suffix)!==-1){ + capabilities.unrestricted = capabilities.canDelete = false + if (suffix == 'edit') { + capabilities.canAnnotate = capabilities.canEdit = false + } + } + return capabilities +} + /** * saveManyRelations, queueManyRelations, runManyRelations * A basic Ajax queue for saving relationships, as the save API presently saves one relationship at a time @@ -2595,13 +2618,11 @@ function ScalarModel(options) { var me = this; - this.urlPrefix; // home page of the book - this.parent_uri = options['parent_uri']; // parent uri of the book - this.logged_in = options['logged_in']; // is the user currently logged in - this.user_level = options['user_level']; // level of the current user - this.nodes = []; // all known nodes - this.nodesByURL = {}; // all known nodes, indexed by their URLs - this.nodesByURN = {}; // all known nodes, indexed by their URNs + this.urlPrefix; // home page of the book + this.parent_uri = $('link#parent').attr('href'), // parent uri of the book + this.nodes = []; // all known nodes + this.nodesByURL = {}; // all known nodes, indexed by their URLs + this.nodesByURN = {}; // all known nodes, indexed by their URNs this.relationsById = {}; // all known relations, indexed by their ids this.crossDomain = false; // are we making cross-domain requests for testing purposes? @@ -2715,7 +2736,7 @@ function ScalarModel(options) { // figure out where we are if (!this.crossDomain) { - this.urlPrefix = options['parent_uri']; + this.urlPrefix = this.parent_uri; } // scrape book title from page @@ -2744,6 +2765,13 @@ ScalarModel.prototype.scalarTypes = null; ScalarModel.prototype.relationTypes = null; ScalarModel.prototype.userTypes = null; +ScalarModel.prototype.getUser = function() { + if (!this.user) { + this.user = new ScalarUser() + } + return this.user +} + /** * Parses the specified json, creating/updating any referenced nodes. * @private @@ -3126,6 +3154,39 @@ ScalarModel.prototype.getPublisherNode = function() { return this.nodesByURL[this.urlPrefix+'publisher']; } +function ScalarUser() { + let me = this + this.logged_in = $('link#logged_in').attr('href') ? $('link#logged_in').attr('href') : false + this.user_level = $('link#user_level').attr('href') + this.role = ScalarRole.Unknown + if (this.user_level?.length > 0) { + switch (this.user_level) { + case 'scalar:Reader': + this.role = ScalarRole.Author + break + case 'scalar:Commentator': + this.role = ScalarRole.Commentator + break + case 'scalar:Reviewer': + this.role = ScalarRole.Reviewer + break + case 'scalar:Author': + this.role = ScalarRole.Author + break + case 'scalar:Editor': + this.role = ScalarRole.Editor + break + default: + this.role = ScalarRole.Unknown + break + } + } +} + +ScalarUser.prototype.logged_in = null; +ScalarUser.prototype.user_level = null; +ScalarUser.prototype.role = null; + /** * Creates a new ScalarNode. * @constructor Represents a Scalar content node. @@ -3755,7 +3816,6 @@ function ScalarVersion(data, node) { var me = this; this.auxProperties = {}; - this.parseData(data, node); } diff --git a/system/application/views/widgets/nav/jquery.scalarnav.js b/system/application/views/widgets/nav/jquery.scalarnav.js index 3e46d3c03..f2de01bc3 100755 --- a/system/application/views/widgets/nav/jquery.scalarnav.js +++ b/system/application/views/widgets/nav/jquery.scalarnav.js @@ -97,7 +97,7 @@ */ jQuery.NavModel.prototype.init = function() { - switch (scalarapi.model.user_level) { + switch (scalarapi.model.getUser().user_level) { // these roles get an added "import" option in the menu case "scalar:Author": @@ -819,7 +819,7 @@ this.element = this.element.add(this.createButton('comment', 'incoming')); // add editing buttons if the user has the right privileges - if (((scalarapi.model.user_level == "scalar:Author") || (scalarapi.model.user_level == "scalar:Commentator") || (scalarapi.model.user_level == "scalar:Reviewer")) && (window.location.href.substr(window.location.href.length - 5) != '.edit')) { + if (((scalarapi.model.getUser().user_level == "scalar:Author") || (scalarapi.model.getUser().user_level == "scalar:Commentator") || (scalarapi.model.getUser().user_level == "scalar:Reviewer")) && (window.location.href.substr(window.location.href.length - 5) != '.edit')) { this.element = this.element.add('

New Edit Hide

').appendTo(this.model.element); this.element.find('.utility_button').eq(2).on('click', this.handleDelete); } @@ -1140,7 +1140,7 @@ this.element = this.element.add(tagBtn); // user's guide button, only shown to those with authoring privileges - if ((scalarapi.model.user_level == "scalar:Author") || (scalarapi.model.user_level == "scalar:Commentator") || (scalarapi.model.user_level == "scalar:Reviewer")) { + if ((scalarapi.model.getUser().user_level == "scalar:Author") || (scalarapi.model.getUser().user_level == "scalar:Commentator") || (scalarapi.model.getUser().user_level == "scalar:Reviewer")) { var guideBtn = $('

User\'s Guide

'); guideBtn.data('url', 'http://scalar.usc.edu/works/guide/'); guideBtn.on('click', function() { window.open($(this).data('url'), '_blank'); }); From f25a2219976add40792b50fd32079cdbeafc157d Mon Sep 17 00:00:00 2001 From: Erik Loyer Date: Sun, 19 Feb 2023 09:30:33 -0800 Subject: [PATCH 02/31] Add add'l user functions and model properties --- .../views/widgets/api/scalarapi.js | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/system/application/views/widgets/api/scalarapi.js b/system/application/views/widgets/api/scalarapi.js index c1ca291bb..e43b9fbed 100755 --- a/system/application/views/widgets/api/scalarapi.js +++ b/system/application/views/widgets/api/scalarapi.js @@ -26,6 +26,16 @@ const ScalarRole = { Editor: 'editor', } +const ScalarEditorialState = { + None: "none", + Draft: "draft", + Edit: "edit", + EditReview: "editreview", + Clean: "clean", + Ready: "ready", + Published: "published", +} + var scalarapi = new ScalarAPI(); function is_array(input){ @@ -2619,13 +2629,28 @@ function ScalarModel(options) { var me = this; this.urlPrefix; // home page of the book - this.parent_uri = $('link#parent').attr('href'), // parent uri of the book + this.parent_uri = $('link#parent').attr('href') + this.bookId = parseInt($('link#book_id').attr('href')) + this.usingHypothesis = $('link#hypothesis').attr('href') === 'true' this.nodes = []; // all known nodes this.nodesByURL = {}; // all known nodes, indexed by their URLs this.nodesByURN = {}; // all known nodes, indexed by their URNs this.relationsById = {}; // all known relations, indexed by their ids this.crossDomain = false; // are we making cross-domain requests for testing purposes? + this.isEditorialPathPage = $('.editorialpath-page>#editorialPath').length > 0; + this.editorialWorkflowEnabled = $('link#editorial_workflow').attr('href') === 'true'; + this.editorialState = ScalarEditorialState.None + if (this.editorialWorkflowEnabled) { + if ($('header span.metadata[property="scalar:editorialState"]').length > 0) { + let state = $('header span.metadata[property="scalar:editorialState"]').text() + state = state[0].toUpperCase() + state.slice(1).toLowerCase() + this.editorialState = ScalarEditorialState[state]; + } else { + base.editorialState = ScalarEditorialState.Draft; + } + } + // list of namespaces and the prefixes used by Scalar, grabbed from xmlns attributes of tag // TODO: "the usage of 'xmlns' for prefix definition is deprecated; please use the 'prefix' attribute instead" this.namespaces = {}; @@ -3187,6 +3212,28 @@ ScalarUser.prototype.logged_in = null; ScalarUser.prototype.user_level = null; ScalarUser.prototype.role = null; +ScalarUser.prototype.canEdit = function() { + const roles = [ScalarRole.Author, ScalarRole.commentator, ScalarRole.Reviewer, ScalarRole.Editor] + return roles.indexOf(this.role) != -1 +} + +ScalarUser.prototype.canAdd = function() { + const roles = [ScalarRole.Author, ScalarRole.commentator] + return roles.indexOf(this.role) != -1 +} + +ScalarUser.prototype.canDelete = function() { + const roles = [ScalarRole.Author, ScalarRole.commentator] + const states = [ScalarEditorialState.Edit, ScalarEditorialState.Clean, ScalarEditorialState.Published] + return roles.indexOf(this.role) != -1 && states.indexOf(scalarapi.model.editorialState) == -1 +} + +ScalarUser.prototype.canCopyEdit = function() { + const roles = [ScalarRole.Author, ScalarRole.commentator] + const states = [ScalarEditorialState.Edit, ScalarEditorialState.Clean, ScalarEditorialState.Ready] + return roles.indexOf(this.role) != -1 && states.indexOf(scalarapi.model.editorialState) == -1 +} + /** * Creates a new ScalarNode. * @constructor Represents a Scalar content node. From faadd89194b5221ddc3eeb45e15a637c5aae8269 Mon Sep 17 00:00:00 2001 From: Erik Loyer Date: Tue, 21 Feb 2023 08:53:16 -0800 Subject: [PATCH 03/31] Begin refactoring --- .../cantaloupe/js/scalarheader-new.jquery.js | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 system/application/views/melons/cantaloupe/js/scalarheader-new.jquery.js diff --git a/system/application/views/melons/cantaloupe/js/scalarheader-new.jquery.js b/system/application/views/melons/cantaloupe/js/scalarheader-new.jquery.js new file mode 100644 index 000000000..1a3722811 --- /dev/null +++ b/system/application/views/melons/cantaloupe/js/scalarheader-new.jquery.js @@ -0,0 +1,98 @@ +/** + * Scalar + * Copyright 2013 The Alliance for Networking Visual Culture. + * http://scalar.usc.edu/scalar + * Alliance4NVC@gmail.com + * + * Licensed under the Educational Community License, Version 2.0 + * (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.osedu.org/licenses /ECL-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +(function($){ + $.scalarheader = function(el, options){ + var base = this + base.$el = $(el) + base.el = el + + base.init = function() { + base.options = $.extend({},$.scalarheader.defaultOptions, options); + base.currentNode = scalarapi.model.getCurrentPageNode(); + + // add classes and attributes + if (scalarapi.model.getUser().canEdit()) base.$el.addClass('edit_enabled'); + if (scalarapi.model.usingHypothesis) base.$el.addClass('hypothesis_active'); + base.$el.addClass('text-uppercase heading_font navbar navbar-inverse navbar-fixed-top').attr('id','scalarheader'); + //Pop the title link DOM element off for a minute - we'll use this again later on. + var title_link = base.$el.find('#book-title').addClass('navbar-link').detach().attr('id','').addClass('book-title'); + + let navbar = $('
') + navbar.append(base.mobileHeader()) + navbar.append(base.desktopHeader()) + + base.$el.append(navbar) + } + + base.mobileHeader = function() { + let mobileHeader = $('') + // this is where the title will go + mobileHeader.append('') + mobileHeader.append(base.hamburgerMenu()) + return mobileHeader + } + + base.hamburgerMenu = function() { + return $('') + } + + base.desktopHeader = function() { + let desktopHeader = $('') + desktopHeader.append(base.navigationMenus()) + return desktopHeader + } + + base.navigationMenus = function() { + let navigationMenus = $('') + + // add top level 'home' link, visible only on mobile + navigationMenus.append('
  • '+ + ''+ + 'Home Page'+ + ''+ + '
  • ') + + // STOPPED AT LINE 222 of scalarheader + + navigationMenus.append(base.mainMenu()) + return navigationMenus + } + + base.mainMenu = function() { + let mainMenu = + } + + base.init() + + } + + $.scalarheader.defaultOptions = { + root_url: '' + } + + $.fn.scalarheader = function(options){ + return new $.scalarheader(this, options); + } + +})(jQuery) \ No newline at end of file From 0fb9eceb138cee4034223e87b7a609a1f321bcac Mon Sep 17 00:00:00 2001 From: Erik Loyer Date: Wed, 22 Feb 2023 08:58:06 -0800 Subject: [PATCH 04/31] Main and wayfinding menus --- .../views/melons/cantaloupe/css/header.css | 2 +- .../cantaloupe/js/scalarheader-new.jquery.js | 182 +++++++++++++++++- 2 files changed, 174 insertions(+), 10 deletions(-) diff --git a/system/application/views/melons/cantaloupe/css/header.css b/system/application/views/melons/cantaloupe/css/header.css index 0f83e9570..dec3f7c85 100644 --- a/system/application/views/melons/cantaloupe/css/header.css +++ b/system/application/views/melons/cantaloupe/css/header.css @@ -651,7 +651,7 @@ span.navbar-text.navbar-left.pull-left.title_wrapper.visible-xs{ .navbar ul.dropdown-menu li{ max-width: 38rem; } -.navbar ul.dropdown-menu li.vis_link{ +#vis_menu li{ max-width: inherit; } .navbar .dropdown-menu li:hover, .navbar .dropdown-menu li:active{ diff --git a/system/application/views/melons/cantaloupe/js/scalarheader-new.jquery.js b/system/application/views/melons/cantaloupe/js/scalarheader-new.jquery.js index 1a3722811..40eda0aad 100644 --- a/system/application/views/melons/cantaloupe/js/scalarheader-new.jquery.js +++ b/system/application/views/melons/cantaloupe/js/scalarheader-new.jquery.js @@ -31,7 +31,7 @@ if (scalarapi.model.usingHypothesis) base.$el.addClass('hypothesis_active'); base.$el.addClass('text-uppercase heading_font navbar navbar-inverse navbar-fixed-top').attr('id','scalarheader'); //Pop the title link DOM element off for a minute - we'll use this again later on. - var title_link = base.$el.find('#book-title').addClass('navbar-link').detach().attr('id','').addClass('book-title'); + base.title_link = base.$el.find('#book-title').addClass('navbar-link').detach().attr('id','').addClass('book-title'); let navbar = $('
    ') navbar.append(base.mobileHeader()) @@ -66,21 +66,185 @@ base.navigationMenus = function() { let navigationMenus = $('') - // add top level 'home' link, visible only on mobile - navigationMenus.append('
  • '+ - ''+ + navigationMenus.append(base.homeLink()) + navigationMenus.append(base.mainMenu()) + navigationMenus.append(base.wayfindingMenu()) + + return navigationMenus + } + + base.homeLink = function() { + // home link is visible only on mobile + let homeLink = $('
  • '+ + ''+ 'Home Page'+ ''+ '
  • ') + return homeLink + } + + base.mainMenu = function() { + let mainMenu = $('') + return mainMenu + } - // STOPPED AT LINE 222 of scalarheader + base.wayfindingMenu = function() { + let visualizationMenuData = [ + { + id: 'viscurrent', + text: 'Current', + icon: 'currentIcon', + }, + { + id: 'vistoc', + text: 'Contents', + icon: 'tocIcon', + }, + { + id: 'visconnections', + text: 'Connections', + icon: 'connectionsIcon', + }, + { + id: 'visindex', + text: 'Grid', + icon: 'gridIcon', + }, + { + id: 'vismap', + text: 'Map', + icon: 'mapIcon', + }, + { + id: 'visradial', + text: 'Radial', + icon: 'radialIcon', + }, + { + id: 'vispath', + text: 'Path', + icon: 'pathIcon', + }, + { + id: 'vismedia', + text: 'Media', + icon: 'mediaIcon', + }, + { + id: 'vistag', + text: 'Tag', + icon: 'tagIcon', + }, + { + id: 'viswordcloud', + text: 'Word Cloud', + icon: 'wordCloudIcon', + }, + ] + let index_url = scalarapi.model.parent_uri.slice(0, -1); + index_url = index_url.substr(0, index_url.lastIndexOf('/'))+'/'; + let scalarMenuData = [ + { + text: 'About Scalar', + url: 'https://scalar.usc.edu/', + target: '_scalar', + }, + { + text: 'User’s Guide', + url: 'https://scalar.usc.edu/works/guide2', + target: '_scalar', + }, + { + text: 'More Scalar Projects', + url: base.applyCurrentQueryVarsToURL(index_url), + target: '_scalar', + }, + ] + let wayfindingMenu = $('') + for (let i=0; i ${menuItem.text}`) + } + + base.linkMenuItem = function(menuItem) { + return $(`
  • ${menuItem.text}
  • `) + } + + base.applyCurrentQueryVarsToURL = function(url){ + // @TODO: Add non-book related query vars as well + var allowableVars = ['path','m']; + const queryVars = scalarapi.getQueryVars(window.location.href) + let currentVar + for (let i=0; i' + }, + { + id: 'lenses_menu', + text: 'Lenses', + icon: 'lensIcon', + }, + { + id: 'vis_menu', + text: 'Visualizations', + icon: 'visIcon', + submenu: [ + { + text: 'Current', + icon: 'currentIcon', + data: { vistype: 'viscurrent' }, + }, + { + text: 'Contents', + icon: 'tocIcon', + data: { vistype: 'vistoc' }, + }, + { + text: 'Connections', + icon: 'connectionsIcon', + data: { vistype: 'visconnections' }, + }, + { + text: 'Grid', + icon: 'gridIcon', + data: { vistype: 'visindex' }, + }, + { + text: 'Map', + icon: 'mapIcon', + data: { vistype: 'vismap' }, + }, + { + text: 'Radial', + icon: 'radialIcon', + data: { vistype: 'visradial' }, + }, + { + text: 'Path', + icon: 'pathIcon', + data: { vistype: 'vispath' }, + }, + { + text: 'Media', + icon: 'mediaIcon', + data: { vistype: 'vismedia' }, + }, + { + text: 'Tag', + icon: 'tagIcon', + data: { vistype: 'vistag' }, + }, + { + text: 'Word Cloud', + icon: 'wordCloudIcon', + data: { vistype: 'viswordcloud' }, + }, + ], + }, + { + id: 'scalar_menu', + text: 'Scalar', + icon: 'scalarIcon', + submenu: [ + { + text: 'About Scalar', + url: 'https://scalar.usc.edu/', + target: '_scalar', + }, + { + text: 'User’s Guide', + url: 'https://scalar.usc.edu/works/guide2', + target: '_scalar', + }, + { + text: 'More Scalar Projects', + url: base.applyCurrentQueryVarsToURL(base.index_url), + target: '_scalar', + }, + ], + }, + ] + } // add classes and attributes if (scalarapi.model.getUser().canEdit()) base.$el.addClass('edit_enabled'); @@ -110,122 +207,58 @@ } base.wayfindingMenu = function() { - let visualizationMenuData = [ - { - id: 'viscurrent', - text: 'Current', - icon: 'currentIcon', - }, - { - id: 'vistoc', - text: 'Contents', - icon: 'tocIcon', - }, - { - id: 'visconnections', - text: 'Connections', - icon: 'connectionsIcon', - }, - { - id: 'visindex', - text: 'Grid', - icon: 'gridIcon', - }, - { - id: 'vismap', - text: 'Map', - icon: 'mapIcon', - }, - { - id: 'visradial', - text: 'Radial', - icon: 'radialIcon', - }, - { - id: 'vispath', - text: 'Path', - icon: 'pathIcon', - }, - { - id: 'vismedia', - text: 'Media', - icon: 'mediaIcon', - }, - { - id: 'vistag', - text: 'Tag', - icon: 'tagIcon', - }, - { - id: 'viswordcloud', - text: 'Word Cloud', - icon: 'wordCloudIcon', - }, - ] - let index_url = scalarapi.model.parent_uri.slice(0, -1); - index_url = index_url.substr(0, index_url.lastIndexOf('/'))+'/'; - let scalarMenuData = [ - { - text: 'About Scalar', - url: 'https://scalar.usc.edu/', - target: '_scalar', - }, - { - text: 'User’s Guide', - url: 'https://scalar.usc.edu/works/guide2', - target: '_scalar', - }, - { - text: 'More Scalar Projects', - url: base.applyCurrentQueryVarsToURL(index_url), - target: '_scalar', - }, - ] let wayfindingMenu = $('') - for (let i=0; i ${menuItem.text}`) + // STOPPED at line 290 + + base.menuItem = function(itemData) { + let listItem = $(``) + let link = $('') + if (itemData.icon) { + link.append(``) + } + link.append(itemData.text) + listItem.append(link) + if (itemData.submenu) { + submenu = $('