Skip to content
Snippets Groups Projects
Commit b7303e82 authored by iaibrys's avatar iaibrys
Browse files

Move coffeescript to ES6; add webpack

parent 8858af23
No related branches found
No related tags found
No related merge requests found
Pipeline #321 failed
Showing
with 937 additions and 998 deletions
defaults
......@@ -7,7 +7,6 @@ pdf.js/node_modules
LICENSE
tmp/
log/
bin/
**/*.sqlite3
**/node_modules
vendor/assets/components/
......
**/*{.,-}min.js
node_modules/
deps/**/*
assets/**/*
public/**/*
module.exports = {
"extends": ["eslint:recommended", "airbnb-base"],
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"jquery": true
},
"globals": {
},
rules: {
'no-console': 'off',
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"],
"class-methods-use-this": 'off',
"no-param-reassign": 'off',
"max-len": ["error", 120, 2],
},
};
---
parserOptions:
sourceType: module
ecmaFeatures:
jsx: true
env:
amd: true
browser: true
es6: true
jquery: true
node: true
# http://eslint.org/docs/rules/
rules:
# Possible Errors
no-await-in-loop: off
no-cond-assign: error
no-console: off
no-constant-condition: error
no-control-regex: error
no-debugger: error
no-dupe-args: error
no-dupe-keys: error
no-duplicate-case: error
no-empty-character-class: error
no-empty: error
no-ex-assign: error
no-extra-boolean-cast: error
no-extra-parens: off
no-extra-semi: error
no-func-assign: error
no-inner-declarations:
- error
- functions
no-invalid-regexp: error
no-irregular-whitespace: error
no-negated-in-lhs: error
no-obj-calls: error
no-prototype-builtins: off
no-regex-spaces: error
no-sparse-arrays: error
no-template-curly-in-string: off
no-unexpected-multiline: error
no-unreachable: error
no-unsafe-finally: off
no-unsafe-negation: off
use-isnan: error
valid-jsdoc: off
valid-typeof: error
# Best Practices
accessor-pairs: error
array-callback-return: off
block-scoped-var: off
class-methods-use-this: off
complexity:
- error
- 6
consistent-return: off
curly: off
default-case: off
dot-location: off
dot-notation: off
eqeqeq: error
guard-for-in: error
no-alert: error
no-caller: error
no-case-declarations: error
no-div-regex: error
no-else-return: off
no-empty-function: off
no-empty-pattern: error
no-eq-null: error
no-eval: error
no-extend-native: error
no-extra-bind: error
no-extra-label: off
no-fallthrough: error
no-floating-decimal: off
no-global-assign: off
no-implicit-coercion: off
no-implied-eval: error
no-invalid-this: off
no-iterator: error
no-labels:
- error
- allowLoop: true
allowSwitch: true
no-lone-blocks: error
no-loop-func: error
no-magic-number: off
no-multi-spaces: off
no-multi-str: off
no-native-reassign: error
no-new-func: error
no-new-wrappers: error
no-new: error
no-octal-escape: error
no-octal: error
no-param-reassign: off
no-proto: error
no-redeclare: error
no-restricted-properties: off
no-return-assign: error
no-return-await: off
no-script-url: error
no-self-assign: off
no-self-compare: error
no-sequences: off
no-throw-literal: off
no-unmodified-loop-condition: off
no-unused-expressions: error
no-unused-labels: off
no-useless-call: error
no-useless-concat: error
no-useless-escape: off
no-useless-return: off
no-void: error
no-warning-comments: off
no-with: error
prefer-promise-reject-errors: off
radix: error
require-await: off
vars-on-top: off
wrap-iife: error
yoda: off
# Strict
strict: off
# Variables
init-declarations: off
no-catch-shadow: error
no-delete-var: error
no-label-var: error
no-restricted-globals: off
no-shadow-restricted-names: error
no-shadow: off
no-undef-init: error
no-undef: off
no-undefined: off
no-unused-vars: off
no-use-before-define: off
# Node.js and CommonJS
callback-return: error
global-require: error
handle-callback-err: error
no-mixed-requires: off
no-new-require: off
no-path-concat: error
no-process-env: off
no-process-exit: error
no-restricted-modules: off
no-sync: off
# Stylistic Issues
array-bracket-spacing: off
block-spacing: off
brace-style: off
camelcase: off
capitalized-comments: off
comma-dangle:
- error
- never
comma-spacing: off
comma-style: off
computed-property-spacing: off
consistent-this: off
eol-last: off
func-call-spacing: off
func-name-matching: off
func-names: off
func-style: off
id-length: off
id-match: off
indent: off
jsx-quotes: off
key-spacing: off
keyword-spacing: off
line-comment-position: off
linebreak-style: off
lines-around-comment: off
lines-around-directive: off
max-depth: off
max-len: off
max-nested-callbacks: off
max-params: off
max-statements-per-line: off
max-statements:
- error
- 30
multiline-ternary: off
new-cap: off
new-parens: off
newline-after-var: off
newline-before-return: off
newline-per-chained-call: off
no-array-constructor: off
no-bitwise: off
no-continue: off
no-inline-comments: off
no-lonely-if: off
no-mixed-operators: off
no-mixed-spaces-and-tabs: off
no-multi-assign: off
no-multiple-empty-lines: off
no-negated-condition: off
no-nested-ternary: off
no-new-object: off
no-plusplus: off
no-restricted-syntax: off
no-spaced-func: off
no-tabs: off
no-ternary: off
no-trailing-spaces: off
no-underscore-dangle: off
no-unneeded-ternary: off
object-curly-newline: off
object-curly-spacing: off
object-property-newline: off
one-var-declaration-per-line: off
one-var: off
operator-assignment: off
operator-linebreak: off
padded-blocks: off
quote-props: off
quotes: off
require-jsdoc: off
semi-spacing: off
semi: off
sort-keys: off
sort-vars: off
space-before-blocks: off
space-before-function-paren: off
space-in-parens: off
space-infix-ops: off
space-unary-ops: off
spaced-comment: off
template-tag-spacing: off
unicode-bom: off
wrap-regex: off
# ECMAScript 6
arrow-body-style: off
arrow-parens: off
arrow-spacing: off
constructor-super: off
generator-star-spacing: off
no-class-assign: off
no-confusing-arrow: off
no-const-assign: off
no-dupe-class-members: off
no-duplicate-imports: off
no-new-symbol: off
no-restricted-imports: off
no-this-before-super: off
no-useless-computed-key: off
no-useless-constructor: off
no-useless-rename: off
no-var: off
object-shorthand: off
prefer-arrow-callback: off
prefer-const: off
prefer-destructuring: off
prefer-numeric-literals: off
prefer-rest-params: off
prefer-reflect: off
prefer-spread: off
prefer-template: off
require-yield: off
rest-spread-spacing: off
sort-imports: off
symbol-description: off
template-curly-spacing: off
yield-star-spacing: off
......@@ -7,7 +7,6 @@ log
tmp
db/*.sqlite3
/external.env
bin/
vendor/assets/components
public/assets/
public/vendor/
......@@ -18,3 +17,10 @@ public/assets/Agendas.md
spec/files/data
spec/files/test_file*.json
/public/packs
/public/packs-test
/node_modules
/yarn-error.log
yarn-debug.log*
.yarn-integrity
{
/*
* ENVIRONMENTS
* =================
*/
// Define globals exposed by modern browsers.
"browser": true,
// Define globals exposed by jQuery.
"jquery": true,
// Define globals exposed by Node.js.
"node": true,
// Allow ES6.
"esversion": 6,
/*
* ENFORCING OPTIONS
* =================
*/
// Force all variable names to use either camelCase style or UPPER_CASE
// with underscores.
"camelcase": true,
// Prohibit use of == and != in favor of === and !==.
"eqeqeq": true,
// Enforce tab width of 2 spaces.
"indent": 2,
// Prohibit use of a variable before it is defined.
"latedef": true,
// Enforce line length to 100 characters
"maxlen": 100,
// Require capitalized names for constructor functions.
"newcap": true,
// Enforce use of single quotation marks for strings.
"quotmark": "single",
// Enforce placing 'use strict' at the top function scope
"strict": true,
// Prohibit use of explicitly undeclared variables.
"undef": true,
// Warn when variables are defined but never used.
"unused": true,
/*
* RELAXING OPTIONS
* =================
*/
// Suppress warnings about == null comparisons.
"eqnull": true
}
......@@ -27,18 +27,29 @@ RUN sudo apt-get -y install redis
RUN sudo apt-get -y install imagemagick
RUN sudo apt-get -y install libsqlite3-dev
# https://github.com/yarnpkg/yarn/issues/3189
#RUN sudo apt-get remove cmdinstall
RUN sudo apt update
RUN sudo apt remove -y cmdtest
RUN sudo apt remove -y yarn
RUN sudo npm install -g yarn
RUN yarn --version
# Install the necessary gems
COPY deps ${APP_ROOT}/deps
COPY Gemfile ${APP_ROOT}/Gemfile
COPY Rakefile ${APP_ROOT}/Rakefile
COPY Gemfile ${APP_ROOT}/Gemfile.lock
RUN sudo chown ${CHOWN} Gemfile*
RUN sudo chown -R ${CHOWN} ${APP_ROOT}/deps
RUN bundle install --without development
RUN bundle install --without development --binstubs
# Install client-side components
COPY package.json ${APP_ROOT}/package.json
RUN npm install
RUN sudo chown -R ${CHOWN} ~/.npm
RUN sudo chown -R ${CHOWN} ~/.config
RUN yarn install
# Install bower
COPY .bowerrc ${APP_ROOT}/
......@@ -74,11 +85,12 @@ COPY ./deps ${APP_ROOT}/deps
COPY ./config ${APP_ROOT}/config
COPY ./db ${APP_ROOT}/db
COPY ./lib ${APP_ROOT}/lib
COPY ./bin ${APP_ROOT}/bin
COPY ./public-maintenance ${APP_ROOT}/public-maintenance
COPY ./scripts ${APP_ROOT}/scripts
COPY ./spec ${APP_ROOT}/spec
COPY ./test ${APP_ROOT}/test
COPY maintenance.ru config.ru .rspec Procfile Rakefile .bowerrc ${APP_ROOT}/
COPY maintenance.ru config.ru .rspec Procfile Rakefile .bowerrc postcss.config.js ${APP_ROOT}/
RUN sudo chown -R ${CHOWN} ${APP_ROOT}/spec/files
RUN sudo chown -R ${CHOWN} ${APP_ROOT}/db
RUN sudo chown -R ${CHOWN} ${APP_ROOT}/scripts
......
......@@ -7,7 +7,10 @@ ruby '2.5.1'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.1.4'
# gem 'sprockets', '3.6.3' # https://github.com/rails/sass-rails/issues/381
gem 'sprockets', '~> 3.7'
# gem 'sprockets', '~> 3.7'
gem 'sprockets','~> 4.0.0.beta8'
gem 'webpacker', '~> 4.x'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
......
......@@ -68,7 +68,6 @@ GEM
bindex (0.5.0)
bower-rails (0.11.0)
builder (3.2.3)
byebug (11.0.0)
capybara (3.13.2)
addressable
mini_mime (>= 0.1.3)
......@@ -86,7 +85,7 @@ GEM
execjs
coffee-script-source (1.12.2)
commonjs (0.2.7)
concurrent-ruby (1.1.4)
concurrent-ruby (1.1.5)
crass (1.0.4)
daemons (1.3.1)
database_cleaner (1.7.0)
......@@ -193,9 +192,11 @@ GEM
method_source (~> 0.9.0)
psych (3.1.0)
public_suffix (3.0.3)
rack (2.0.6)
rack (2.0.7)
rack-cache (1.8.0)
rack (>= 0.4)
rack-proxy (0.6.5)
rack
rack-rewrite (1.5.1)
rack-test (1.1.0)
rack (>= 1.0, < 3)
......@@ -316,7 +317,7 @@ GEM
shellany (0.0.1)
spring (2.0.2)
activesupport (>= 4.2)
sprockets (3.7.2)
sprockets (4.0.0.beta8)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.1)
......@@ -347,6 +348,10 @@ GEM
activemodel (>= 5.0)
bindex (>= 0.4.0)
railties (>= 5.0)
webpacker (4.0.2)
activesupport (>= 4.2)
rack-proxy (>= 0.6.1)
railties (>= 4.2)
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
......@@ -362,7 +367,6 @@ DEPENDENCIES
addressable
awesome_print (~> 1.6, >= 1.6.1)
bower-rails
byebug
capybara
coffee-rails (~> 4.2.2)
database_cleaner
......@@ -402,12 +406,13 @@ DEPENDENCIES
sass-rails (~> 5.0)
sdoc (~> 0.4.0)
spring
sprockets (~> 3.7)
sprockets (~> 4.0.0.beta8)
sqlite3 (~> 1.3.0)
therubyracer
thin (~> 1.7.2)
uglifier (>= 1.3.0)
web-console (~> 3.5.1)
webpacker (~> 4.x)
RUBY VERSION
ruby 2.5.1p57
......
//= link_tree ../images
//= link_directory ../javascripts .js
//= link application.css
#TODO: Move all the loose window functions to the AdminPage class
window.toQuizString = (date) ->
return ("0" + date.getDate()).slice(-2) + '/' + ("0" + (date.getMonth() + 1)).slice(-2) + "-" + ("0" + date.getYear()).slice(-2) + " " + ("0" + date.getHours()).slice(-2) + ":" + ("0" + date.getMinutes()).slice(-2)
window.waitingDialog = window.waitingDialog || ( ($) ->
dialog_html = '''
<div id="admin-modal" class="modal fade" data-backdrop="static" data-keyboard="false"
tabindex="-1" role="dialog" aria-hidden="true"
style="padding-top:15%; overflow-y:scroll;">
<div class="modal-dialog modal-m">
<div class="modal-content">
<div class="modal-header">
<h3 style="margin:0;"></h3>
</div>
<div class="modal-body">
<div class="progress progress-striped active" style="margin-bottom:0;">
<div class="progress-bar" style="width: 100%">
</div>
</div>
</div>
</div>
</div>
</div>
'''
$dialog = $(dialog_html)
x = {
show: (message, options) ->
options = {} if not options?
message = 'Loading' if not message?
settings = $.extend({
dialogSize: 'm',
progressType: '',
onHide: null
}, options)
$dialog.find('.modal-dialog').
attr('class', 'modal-dialog').
addClass('modal-' + settings.dialogSize)
$dialog.find('.progress-bar').attr('class', 'progress-bar')
if settings.progressType
$dialog.find('.progress-bar').
addClass('progress-bar-' + settings.progressType)
$dialog.find('h3').text(message)
if typeof settings.onHide is 'function'
$dialog.off('hidden.bs.modal').on('hidden.bs.modal', (e) ->
settings.onHide.call($dialog)
)
$dialog.modal()
hide: () ->
$dialog.modal('hide')
}
return x
)(jQuery)
window.startGetPDFJobPoller = (jobid) ->
console.log('Starting getpdf job poll for ' + jobid)
max_counts = 100
poller = () ->
success = (data) ->
waitingDialog.hide()
console.log('Success in getting getpdf job poll ' + max_counts)
console.dir(data)
if !data.hasOwnProperty('status')
console.log "No status field!"
return
$('.getpdf_status_'+data._id).text(data.status)
if $('#current-getpdf-job').data('getpdfjobid') == data._id
$('#current-getpdf-job .getpdf_job_status').text(data.status)
$('#current-getpdf-job .getpdf_job_lastRunAt').text(toQuizString(new Date(data.lastRunAt)))
lfa = "N/A"
if data.lastFinishedAt
lfa = toQuizString(new Date(data.lastFinishedAt))
$('#current-getpdf-job .getpdf_job_lastFinishedAt').text(lfa)
if data.status != "running" || max_counts < 0
location.reload()
else
if lfa == "N/A"
setTimeout(poller, 2000)
fail = () ->
console.error('Failed to get getpdf job poll ' + max_counts)
setTimeout(poller, 2000)
path = window.ADMIN_CONFIG.urls.getpdf_job_url
path = path.replace('JOB_ID', jobid)
max_counts -= 1
ajax_parms = {
method: "GET",
url: path,
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
setTimeout(poller, 2000)
window.startAllGetPDFPollers = (running_getpdf) ->
console.dir(running_getpdf)
running_getpdf.forEach (job) ->
window.startGetPDFJobPoller(job._id)
window.updateCourseWebsite = (event, path) ->
success = () ->
console.log('updateCourseWebsite: Success')
waitingDialog.hide()
location.reload()
fail = () ->
console.error('updateCourseWebsite: Failed to update')
waitingDialog.hide()
waitingDialog.show('Updating webNotes...')
ajax_parms = {
method: "GET",
url: path,
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
window.buildEnotes = (event, path) ->
success = (data) ->
console.log('buildEnotes: Success')
waitingDialog.hide()
console.dir(data)
location.hash = 'sharelatex'
$('#current-getpdf-job').data('getpdfjobid', data._id)
startGetPDFJobPoller(data._id)
fail = () ->
console.error('buildEnotes: Failed to compile enotes')
waitingDialog.hide()
waitingDialog.show('Starting eNote Compile...')
ajax_parms = {
method: "GET",
url: path,
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
window.updateEnotes = (event, path) ->
success = () ->
console.log('updateEnotes: Success')
waitingDialog.hide()
location.hash = 'sharelatex'
location.reload()
fail = () ->
console.error('updateEnotes: Failed to update enotes')
waitingDialog.hide()
waitingDialog.show('Updating eNotes...')
ajax_parms = {
method: "GET",
url: path,
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
window.updateFileManager = (event, path) ->
success = () ->
console.log('updateFileManager: Success')
waitingDialog.hide()
location.hash = 'filemanager'
location.reload()
fail = () ->
console.error('updateFileManager: Failed to update file manager')
waitingDialog.hide()
waitingDialog.show('Updating File Manager...')
ajax_parms = {
method: "GET",
url: path,
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
class AdminPage
constructor: (urls, course_id) ->
self = this
if (AdminPage.instance)
console.error "Multiple AdminPage"
return
AdminPage.instance = this
@urls = urls
@course_id = course_id
console?.log "AdminPage page for #{course_id}"
console?.log urls
@getGetPDFJobInfo = (event, path) ->
success = (data) ->
waitingDialog.hide()
console.log('getGetPDFJobInfo: Success')
console.dir(data)
dialog = $("#getpdf_job_logs")
dialog.find(".modal-title").text("Job Logs")
logs = (data?.data?.logs || ['no logs!']).map((x) => x).join("\n")
dialog.find(".modal-body").empty()
dialog.find(".modal-body").html('<pre></pre>')
dialog.find(".modal-body pre").text(logs)
dialog.modal().show();
# location.hash = 'sharelatex'
# location.reload()
fail = () ->
console.error('getGetPDFJobInfo: Failed to update enotes')
waitingDialog.hide()
waitingDialog.show('Getting Conversion Logs')
ajax_parms = {
method: "GET",
url: path
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
@cancelGetPDFJob = (event, path) ->
success = (data) ->
waitingDialog.hide()
location.hash = 'sharelatex'
location.reload()
fail = () ->
console.error('cancelGetPDFJob: Failed to cancel')
waitingDialog.hide()
waitingDialog.show('Cancelling Job')
ajax_parms = {
method: "GET",
url: path,
data: JSON.stringify({}, null, 2)
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
@publishEnotes = (event, path) ->
success = () ->
console.log('publishEnotes: Success')
waitingDialog.hide()
location.hash = 'sharelatex'
location.reload()
fail = () ->
console.error('publishEnotes: Failed to publish eNotes')
waitingDialog.hide()
waitingDialog.show('Publishing eNotes...')
ajax_parms = {
method: "GET",
url: path,
data: JSON.stringify({}, null, 2)
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
@showEnotes = (event, path) ->
event.preventDefault()
success = (data) ->
waitingDialog.hide()
console.log('showEnotes: Success')
dialog = $("#getpdf_job_logs")
if (path.indexOf("staging") == -1)
title = "Enotes"
else
title = "Enotes Staging"
dialog.find(".modal-title").text(title)
dialog.find(".modal-body").html(data)
console.dir(dialog.find(".modal-body"))
dialog.modal().show();
# location.hash = 'sharelatex'
# location.reload()
fail = () ->
console.error('showEnotes: Failed to query eNotes')
waitingDialog.hide()
waitingDialog.show("Getting eNotes")
console.log("Getting eNotes #{path}")
ajax_parms = {
method: "GET",
url: path,
dataType: 'html',
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
#
# course operations
#
@changeCourseNumber = (event, path, url) ->
re = /[0-9]{6}/g
matches = []
loop
m = re.exec(url)
break unless m
delete m.input
matches.push m
if matches.length > 0
new_value = matches[matches.length - 1][0]
else
flash('alert-danger', "URL must be in the form https://cn.inside.dtu.dk/cnnet/element/123456")
return
console.log path
success = (data) ->
console.log('changeCourseNumber: Success')
waitingDialog.hide()
flash('alert-success', data.message)
location.reload()
fail = () ->
console.error('changeCourseNumber: Failed to change course number')
waitingDialog.hide()
flash('alert-danger', 'Failed to update course number')
waitingDialog.show('Change course number...')
ajax_parms = {
method: "GET",
url: path,
data: {number:new_value}
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
#
# feedback operations
#
@thumbnailNotFound = (img) ->
img.onerror = null
$(img).replaceWith($("<p><b>Thumbnail:</b> not found<p>"))
@getSelectedIds = () ->
ids = $('.select-feedback-checkbox:checkbox:checked').map () ->
return parseInt($(this).data('feedback-id')) if $(this).prop('checked')
return (ids.filter (id) -> id?).get()
@selectFeedback = (event) ->
checkbox = $(event.target)
new_allow = checkbox.prop('checked')
console.log "selectFeedback #{new_allow}"
ids = @getSelectedIds()
row = checkbox.closest("tr")
if ids.length == 1
text = row.find("td.feedback-text div").text()
$("#feedback-text").text(text)
$("button.feedback-reply").attr('disabled', false)
$("#feedback-comment").attr('disabled', false)
$("#feedback-comment").text("")
else
$("#feedback-text").text("")
$("button.feedback-reply").attr('disabled', true)
$("#feedback-comment").attr('disabled', true)
@submitFeedback = (event, path) ->
card = $(event.target).closest(".feedback-card")
return unless card
feedback_id = card.data("feedbackid")
comment = card.find("textarea.feedback-reply").val()
status = card.find(".feedback-status").val()
path = path.replace('FEEDBACK_ID', feedback_id)
console.log "submitFeedback c:#{comment} s:#{status}"
success = (data) ->
console.log('submitFeedback: Success')
flash('alert-success', data.message)
window.location.reload()
fail = () ->
console.error('submitFeedback: Failed')
flash('alert-danger', 'Failed to submit response')
ajax_parms = {
method: "PUT",
url: path,
data: JSON.stringify({ reply: comment, status: status })
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
@setFeedbackComment = (event, path) ->
ids = @getSelectedIds()
comment = $("#feedback-comment").val()
if ids.length < 1
return
path = path.replace('FEEDBACK_ID', ids[0].toString())
console.log "setFeedbackComment c:#{comment}"
success = (data) ->
console.log('setFeedbackComment: Success')
flash('alert-success', data.message)
window.location.reload()
fail = () ->
console.error('setFeedbackComment: Failed')
flash('alert-danger', 'Failed to set reply comment')
ajax_parms = {
method: "PUT",
url: path,
data: JSON.stringify({ reply: comment })
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
@deleteAllSelected = (event, path) ->
ids = @getSelectedIds()
console.dir(ids)
success = (data) ->
console.log('deleteAllSelected: Success')
flash('alert-success', data.message)
window.location.reload()
fail = () ->
console.error('deleteAllSelected: Failed')
flash('alert-danger', 'Failed to delete feedbacks')
ajax_parms = {
method: "DELETE",
url: path,
data: JSON.stringify({ ids: ids})
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
@setAllowFeedback = (event, path) ->
new_allow = $(event.target).prop('checked')
console.log "ALLOW #{new_allow}"
success = (data) ->
console.log('setAllowFeedback: Success')
fail = () ->
console.error('setAllowFeedback: Failed')
ajax_parms = {
method: "PUT",
url: path,
data: JSON.stringify({ allow_feedback: new_allow})
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
# TODO: combine this with setAllowFeedback; make sure we have the right JSON payload (allow_chat, allow_feedback)
@setAllowChat = (event, path) ->
new_allow = $(event.target).prop('checked')
console.log "ALLOW #{new_allow}"
success = (data) ->
console.log('setAllowChat: Success')
fail = () ->
console.error('setAllowChat: Failed')
ajax_parms = {
method: "PUT",
url: path,
data: JSON.stringify({ allow_chat: new_allow})
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
@changeFeedbackStatus = (event, path) ->
new_status = $(event.target).val()
console.log new_status
success = (data) ->
console.log('changeFeedbackStatus: Success')
fail = () ->
console.error('changeFeedbackStatus: Failed')
ajax_parms = {
method: "PUT",
url: path,
data: JSON.stringify({ status: new_status})
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
@deleteFeedback = (event, path) ->
console.log path
success = (data) ->
console.log('deleteFeedback: Success')
waitingDialog.hide()
flash('alert-success', data.message)
AdminPage.reloadFeedbackGrid()
fail = () ->
console.error('deleteFeedback: Failed')
waitingDialog.hide()
flash('alert-danger', 'Failed to delete feedback')
waitingDialog.show('Delete feedback ...')
ajax_parms = {
method: "DELETE",
url: path,
data: {}
}
$.ajax(ajax_parms)
.done(success)
.fail(fail)
@reloadFeedbackGrid: () ->
self = this
# document.location.reload(true)
$('.feedback-grid-form').each( (event) ->
$.get($(this).attr("action"), $(this).serialize(), (data) ->
$('.feedback-grid-table').html(data.table)
)
)
#
# Init
#
@isMyPage: () ->
parts = window.location.pathname.split('/').filter( (d) -> return d )
if (parts.length < 1)
return false
else if (parts.length > 1)
return (parts[parts.length-1] == "admin") or (parts[parts.length-2] == "admin")
else
return (parts[parts.length-1] == "admin")
@initPage: () ->
if !window.ADMIN_CONFIG?
console?.log "reloading because page was accessed\
via turbolink and page isn't configured"
window.location.reload()
return
if AdminPage.instance?
AdminPage.instance = null
window.admin_page = new AdminPage(window.ADMIN_CONFIG.urls, window.ADMIN_CONFIG.course_id)
#
# Data
#
@instance: null
window.AdminPage = AdminPage
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS103: Rewrite code to no longer use __guard__
* DS206: Consider reworking classes to avoid initClass
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
//TODO: Move all the loose window functions to the AdminPage class
window.toQuizString = date => (`0${date.getDate()}`).slice(-2) + '/' + (`0${date.getMonth() + 1}`).slice(-2) + "-" + (`0${date.getYear()}`).slice(-2) + " " + (`0${date.getHours()}`).slice(-2) + ":" + (`0${date.getMinutes()}`).slice(-2);
window.waitingDialog = window.waitingDialog || ( function($) {
const dialog_html = `\
<div id="admin-modal" class="modal fade" data-backdrop="static" data-keyboard="false"
tabindex="-1" role="dialog" aria-hidden="true"
style="padding-top:15%; overflow-y:scroll;">
<div class="modal-dialog modal-m">
<div class="modal-content">
<div class="modal-header">
<h3 style="margin:0;"></h3>
</div>
<div class="modal-body">
<div class="progress progress-striped active" style="margin-bottom:0;">
<div class="progress-bar" style="width: 100%">
</div>
</div>
</div>
</div>
</div>
</div>\
`;
const $dialog = $(dialog_html);
const x = {
show(message, options) {
if (!options) { options = {}; }
if (!message) { message = 'Loading'; }
const settings = $.extend({
dialogSize: 'm',
progressType: '',
onHide: null
}, options);
$dialog.find('.modal-dialog').
attr('class', 'modal-dialog').
addClass(`modal-${settings.dialogSize}`);
$dialog.find('.progress-bar').attr('class', 'progress-bar');
if (settings.progressType) {
$dialog.find('.progress-bar').
addClass(`progress-bar-${settings.progressType}`);
}
$dialog.find('h3').text(message);
if (typeof settings.onHide === 'function') {
$dialog.off('hidden.bs.modal').on('hidden.bs.modal', e => settings.onHide.call($dialog));
}
return $dialog.modal();
},
hide() {
return $dialog.modal('hide');
}
};
return x;
}(jQuery));
window.startGetPDFJobPoller = function(jobid) {
console.log(`Starting getpdf job poll for ${jobid}`);
let max_counts = 100;
var poller = function() {
const success = function(data) {
let lfa;
waitingDialog.hide();
console.log(`Success in getting getpdf job poll ${max_counts}`);
console.dir(data);
if (!data.hasOwnProperty('status')) {
console.log("No status field!");
return;
}
$(`.getpdf_status_${data._id}`).text(data.status);
if ($('#current-getpdf-job').data('getpdfjobid') === data._id) {
$('#current-getpdf-job .getpdf_job_status').text(data.status);
$('#current-getpdf-job .getpdf_job_lastRunAt').text(toQuizString(new Date(data.lastRunAt)));
lfa = "N/A";
if (data.lastFinishedAt) {
lfa = toQuizString(new Date(data.lastFinishedAt));
}
$('#current-getpdf-job .getpdf_job_lastFinishedAt').text(lfa);
}
if ((data.status !== "running") || (max_counts < 0)) {
return location.reload();
} else {
if (lfa === "N/A") {
return setTimeout(poller, 2000);
}
}
};
const fail = function() {
console.error(`Failed to get getpdf job poll ${max_counts}`);
return setTimeout(poller, 2000);
};
let path = window.ADMIN_CONFIG.urls.getpdf_job_url;
path = path.replace('JOB_ID', jobid);
max_counts -= 1;
const ajax_parms = {
method: "GET",
url: path
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
};
return setTimeout(poller, 2000);
};
window.startAllGetPDFPollers = function(running_getpdf) {
console.dir(running_getpdf);
return running_getpdf.forEach(job => window.startGetPDFJobPoller(job._id));
};
window.updateCourseWebsite = function(event, path) {
const success = function() {
console.log('updateCourseWebsite: Success');
waitingDialog.hide();
return location.reload();
};
const fail = function() {
console.error('updateCourseWebsite: Failed to update');
return waitingDialog.hide();
};
waitingDialog.show('Updating webNotes...');
const ajax_parms = {
method: "GET",
url: path
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
};
window.buildEnotes = function(event, path) {
const success = function(data) {
console.log('buildEnotes: Success');
waitingDialog.hide();
console.dir(data);
location.hash = 'sharelatex';
$('#current-getpdf-job').data('getpdfjobid', data._id);
return startGetPDFJobPoller(data._id);
};
const fail = function() {
console.error('buildEnotes: Failed to compile enotes');
return waitingDialog.hide();
};
waitingDialog.show('Starting eNote Compile...');
const ajax_parms = {
method: "GET",
url: path
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
};
window.updateEnotes = function(event, path) {
const success = function() {
console.log('updateEnotes: Success');
waitingDialog.hide();
location.hash = 'sharelatex';
return location.reload();
};
const fail = function() {
console.error('updateEnotes: Failed to update enotes');
return waitingDialog.hide();
};
waitingDialog.show('Updating eNotes...');
const ajax_parms = {
method: "GET",
url: path
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
};
window.updateFileManager = function(event, path) {
const success = function() {
console.log('updateFileManager: Success');
waitingDialog.hide();
location.hash = 'filemanager';
return location.reload();
};
const fail = function() {
console.error('updateFileManager: Failed to update file manager');
return waitingDialog.hide();
};
waitingDialog.show('Updating File Manager...');
const ajax_parms = {
method: "GET",
url: path
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
};
class AdminPage {
static initClass() {
//
// Data
//
this.instance = null;
}
constructor(urls, course_id) {
const self = this;
if (AdminPage.instance) {
console.error("Multiple AdminPage");
return;
}
AdminPage.instance = this;
this.urls = urls;
this.course_id = course_id;
if (typeof console !== 'undefined' && console !== null) {
console.log(`AdminPage page for ${course_id}`);
}
if (typeof console !== 'undefined' && console !== null) {
console.log(urls);
}
}
static getGetPDFJobInfo(event, path) {
const success = function(data) {
waitingDialog.hide();
console.log('getGetPDFJobInfo: Success');
console.dir(data);
const dialog = $("#getpdf_job_logs");
dialog.find(".modal-title").text("Job Logs");
const logs = (__guard__(data !== null ? data.data : undefined, x => x.logs) || ['no logs!']).map(x => x).join("\n");
dialog.find(".modal-body").empty();
dialog.find(".modal-body").html('<pre></pre>');
dialog.find(".modal-body pre").text(logs);
return dialog.modal().show();
};
// location.hash = 'sharelatex'
// location.reload()
const fail = function() {
console.error('getGetPDFJobInfo: Failed to update enotes');
return waitingDialog.hide();
};
waitingDialog.show('Getting Conversion Logs');
const ajax_parms = {
method: "GET",
url: path
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
static cancelGetPDFJob(event, path) {
const success = function(data) {
waitingDialog.hide();
location.hash = 'sharelatex';
return location.reload();
};
const fail = function() {
console.error('cancelGetPDFJob: Failed to cancel');
return waitingDialog.hide();
};
waitingDialog.show('Cancelling Job');
const ajax_parms = {
method: "GET",
url: path,
data: JSON.stringify({}, null, 2)
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
static publishEnotes(event, path) {
const success = function() {
console.log('publishEnotes: Success');
waitingDialog.hide();
location.hash = 'sharelatex';
return location.reload();
};
const fail = function() {
console.error('publishEnotes: Failed to publish eNotes');
return waitingDialog.hide();
};
waitingDialog.show('Publishing eNotes...');
const ajax_parms = {
method: "GET",
url: path,
data: JSON.stringify({}, null, 2)
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
static showEnotes(event, path) {
event.preventDefault();
const success = function(data) {
let title;
waitingDialog.hide();
console.log('showEnotes: Success');
const dialog = $("#getpdf_job_logs");
if (path.indexOf("staging") === -1) {
title = "Enotes";
} else {
title = "Enotes Staging";
}
dialog.find(".modal-title").text(title);
dialog.find(".modal-body").html(data);
console.dir(dialog.find(".modal-body"));
return dialog.modal().show();
};
// location.hash = 'sharelatex'
// location.reload()
const fail = function() {
console.error('showEnotes: Failed to query eNotes');
return waitingDialog.hide();
};
waitingDialog.show("Getting eNotes");
console.log(`Getting eNotes ${path}`);
const ajax_parms = {
method: "GET",
url: path,
dataType: 'html'
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
//
// course operations
//
static changeCourseNumber(event, path, url) {
let new_value;
const re = /[0-9]{6}/g;
const matches = [];
while (true) {
const m = re.exec(url);
if (!m) { break; }
delete m.input;
matches.push(m);
}
if (matches.length > 0) {
new_value = matches[matches.length - 1][0];
} else {
flash('alert-danger', "URL must be in the form https://cn.inside.dtu.dk/cnnet/element/123456");
return;
}
console.log(path);
const success = function(data) {
console.log('changeCourseNumber: Success');
waitingDialog.hide();
flash('alert-success', data.message);
return location.reload();
};
const fail = function() {
console.error('changeCourseNumber: Failed to change course number');
waitingDialog.hide();
return flash('alert-danger', 'Failed to update course number');
};
waitingDialog.show('Change course number...');
const ajax_parms = {
method: "GET",
url: path,
data: {number:new_value}
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
//
// feedback operations
//
static thumbnailNotFound(img) {
img.onerror = null;
return $(img).replaceWith($("<p><b>Thumbnail:</b> not found<p>"));
}
static getSelectedIds() {
const ids = $('.select-feedback-checkbox:checkbox:checked').map(function() {
if ($(this).prop('checked')) { return parseInt($(this).data('feedback-id'), 10); }
});
return (ids.filter(id => id != null)).get();
}
static selectFeedback(event) {
let text;
const checkbox = $(event.target);
const new_allow = checkbox.prop('checked');
console.log(`selectFeedback ${new_allow}`);
const ids = this.getSelectedIds();
const row = checkbox.closest("tr");
if (ids.length === 1) {
text = row.find("td.feedback-text div").text();
$("#feedback-text").text(text);
$("button.feedback-reply").attr('disabled', false);
$("#feedback-comment").attr('disabled', false);
return $("#feedback-comment").text("");
} else {
$("#feedback-text").text("");
$("button.feedback-reply").attr('disabled', true);
return $("#feedback-comment").attr('disabled', true);
}
}
static submitFeedback(event, path) {
const card = $(event.target).closest(".feedback-card");
if (!card) { return; }
const feedback_id = card.data("feedbackid");
const comment = card.find("textarea.feedback-reply").val();
const status = card.find(".feedback-status").val();
path = path.replace('FEEDBACK_ID', feedback_id);
console.log(`submitFeedback c:${comment} s:${status}`);
const success = function(data) {
console.log('submitFeedback: Success');
flash('alert-success', data.message);
return window.location.reload();
};
const fail = function() {
console.error('submitFeedback: Failed');
return flash('alert-danger', 'Failed to submit response');
};
const ajax_parms = {
method: "PUT",
url: path,
data: JSON.stringify({ reply: comment, status })
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
static setFeedbackComment(event, path) {
const ids = this.getSelectedIds();
const comment = $("#feedback-comment").val();
if (ids.length < 1) {
return;
}
path = path.replace('FEEDBACK_ID', ids[0].toString());
console.log(`setFeedbackComment c:${comment}`);
const success = function(data) {
console.log('setFeedbackComment: Success');
flash('alert-success', data.message);
return window.location.reload();
};
const fail = function() {
console.error('setFeedbackComment: Failed');
return flash('alert-danger', 'Failed to set reply comment');
};
const ajax_parms = {
method: "PUT",
url: path,
data: JSON.stringify({ reply: comment })
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
static deleteAllSelected(event, path) {
const ids = this.getSelectedIds();
console.dir(ids);
const success = function(data) {
console.log('deleteAllSelected: Success');
flash('alert-success', data.message);
return window.location.reload();
};
const fail = function() {
console.error('deleteAllSelected: Failed');
return flash('alert-danger', 'Failed to delete feedbacks');
};
const ajax_parms = {
method: "DELETE",
url: path,
data: JSON.stringify({ ids})
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
static setAllowFeedback(event, path) {
const new_allow = $(event.target).prop('checked');
console.log(`ALLOW ${new_allow}`);
const success = data => console.log('setAllowFeedback: Success');
const fail = () => console.error('setAllowFeedback: Failed');
const ajax_parms = {
method: "PUT",
url: path,
data: JSON.stringify({ allow_feedback: new_allow})
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
// TODO: combine this with setAllowFeedback; make sure we have the right JSON payload (allow_chat, allow_feedback)
static setAllowChat(event, path) {
const new_allow = $(event.target).prop('checked');
console.log(`ALLOW ${new_allow}`);
const success = data => console.log('setAllowChat: Success');
const fail = () => console.error('setAllowChat: Failed');
const ajax_parms = {
method: "PUT",
url: path,
data: JSON.stringify({ allow_chat: new_allow})
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
static changeFeedbackStatus(event, path) {
const new_status = $(event.target).val();
console.log(new_status);
const success = data => console.log('changeFeedbackStatus: Success');
const fail = () => console.error('changeFeedbackStatus: Failed');
const ajax_parms = {
method: "PUT",
url: path,
data: JSON.stringify({ status: new_status})
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
static deleteFeedback(event, path) {
console.log(path);
const success = function(data) {
console.log('deleteFeedback: Success');
waitingDialog.hide();
flash('alert-success', data.message);
return AdminPage.reloadFeedbackGrid();
};
const fail = function() {
console.error('deleteFeedback: Failed');
waitingDialog.hide();
return flash('alert-danger', 'Failed to delete feedback');
};
waitingDialog.show('Delete feedback ...');
const ajax_parms = {
method: "DELETE",
url: path,
data: {}
};
return $.ajax(ajax_parms)
.done(success)
.fail(fail);
}
static reloadFeedbackGrid() {
const self = this;
// document.location.reload(true)
return $('.feedback-grid-form').each( function(event) {
return $.get($(this).attr("action"), $(this).serialize(), data => $('.feedback-grid-table').html(data.table));
});
}
//
// Init
//
static isMyPage() {
const parts = window.location.pathname.split('/').filter( d => d);
if (parts.length < 1) {
return false;
} else if (parts.length > 1) {
return (parts[parts.length-1] === "admin") || (parts[parts.length-2] === "admin");
} else {
return (parts[parts.length-1] === "admin");
}
}
static initPage() {
if (!window.ADMIN_CONFIG) {
if (typeof console !== 'undefined' && console !== null) {
console.log(`reloading because page was accessed\
via turbolink and page isn't configured`);
}
window.location.reload();
return;
}
if (AdminPage.instance !== null) {
AdminPage.instance = null;
}
window.admin_page = new AdminPage(window.ADMIN_CONFIG.urls, window.ADMIN_CONFIG.course_id);
return window.admin_page;
}
}
AdminPage.initClass();
window.AdminPage = AdminPage;
function __guard__(value, transform) {
return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
}
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
// You can use CoffeeScript in this file: http://coffeescript.org/
window.flash = (kind, message) ->
allowed_kinds = ['alert-success',
'alert-info',
'alert-warning',
'alert-danger']
if (allowed_kinds.indexOf(kind) == -1)
console.error("ASSERT: invalid flash message kind " + kind)
now = new Date()
hh = ('0' + now.getHours()).slice(-2)
mm = ('0' + now.getMinutes()).slice(-2)
timestamp = hh + ":" + mm
flash_msg = $('<div class="alert ' + kind + ' fade in">' +
'<button class="close" data-dismiss="alert">&times;</button>' +
'<b>' + timestamp + '</b>: ' + message + '</div>')
$("#flash-message").empty()
flash_msg.prependTo("#flash-message")
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
window.flash = function(kind, message) {
const allowed_kinds = ['alert-success',
'alert-info',
'alert-warning',
'alert-danger'];
if (allowed_kinds.indexOf(kind) === -1) {
console.error(`ASSERT: invalid flash message kind ${kind}`);
}
const now = new Date();
const hh = (`0${now.getHours()}`).slice(-2);
const mm = (`0${now.getMinutes()}`).slice(-2);
const timestamp = hh + ":" + mm;
const flash_msg = $(`<div class="alert ${kind} fade in">` +
'<button class="close" data-dismiss="alert">&times;</button>' +
'<b>' + timestamp + '</b>: ' + message + '</div>');
$("#flash-message").empty();
return flash_msg.prependTo("#flash-message");
};
class EditingDirectives
constructor: ->
EditingDirectives.instance = this
addDirectivesHandlers: (editorObject, macros) ->
this.macros = macros
converterObject = editorObject.getConverter()
converterObject.hooks.chain("preConversion",
(this.replaceDirectives).bind(this))
replaceDirectives: (text) ->
expanded = text.replace(/^[\#]{7} include (.*)/, (match_text, macro_name) ->
macro = PageEditor.instance.macros[macro_name]
if macro
wrapped = "<div class='hidden inline'>" +
"$$\n" +
macro.split('\n').
filter((x) -> !!x and x.replace(/\s/g, '').length).
map( (x) -> x ).join('\n') +
"$$\n" +
"</div>\n"## c.f. page_compiler_service.rb
else
wrapped = "<div class='error-directive'> " +
macro_name +
" does not exist </div>\n"
return wrapped
)
re = /^[\#]{7}\s*(begin|end):(only|hint|answer|question)([\s0-9a-zA-Z,]*)$/gm
expanded = expanded.replace(re, (match_text, beginend, kind, parameters) ->
# this would be nice, but the sanitizing converter
# removes all this stuff...
wrapped = "<div class='directive-region " + kind + "'>" +
"<div class='directive-region-item-container'>" +
"<div class='directive-region-label'>" +
"<div>" + kind + "</div>" +
"</div>" +
"<div class='directive-region-item'>" +
"<p>" + beginend + "<b>:</b>" + kind + "</p>" +
#(parameters ? "(" + parameters + ")" : "") +
"</div>"+
"</div>" +
"</div>\n"## c.f. page_compiler_service.rb
if (parameters.replace /^\s+|\s+$/g, "")
parameters = "(" + parameters + ")"
wrapped = "<b>" + beginend + "</b>:<b>" +
kind + "</b>" + parameters# + "<br/>"
return wrapped
)
return expanded
macros: null
@instance: null
window.EditingDirectives = EditingDirectives
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS206: Consider reworking classes to avoid initClass
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class EditingDirectives {
static initClass() {
this.prototype.macros = null;
this.instance = null;
}
constructor() {
EditingDirectives.instance = this;
}
addDirectivesHandlers(editorObject, macros) {
this.macros = macros;
const converterObject = editorObject.getConverter();
return converterObject.hooks.chain("preConversion",
(this.replaceDirectives).bind(this));
}
replaceDirectives(text) {
let expanded = text.replace(/^[\#]{7} include (.*)/, function(match_text, macro_name) {
let wrapped;
const macro = PageEditor.instance.macros[macro_name];
if (macro) {
wrapped = "<div class='hidden inline'>" +
"$$\n" +
macro.split('\n').
filter(x => !!x && x.replace(/\s/g, '').length).
map( x => x).join('\n') +
"$$\n" +
"</div>\n";//# c.f. page_compiler_service.rb
} else {
wrapped = "<div class='error-directive'> " +
macro_name +
" does not exist </div>\n";
}
return wrapped;
});
const re = /^[\#]{7}\s*(begin|end):(only|hint|answer|question)([\s0-9a-zA-Z,]*)$/gm;
expanded = expanded.replace(re, function(match_text, beginend, kind, parameters) {
// this would be nice, but the sanitizing converter
// removes all this stuff...
let wrapped = `<div class='directive-region ${kind}'>` +
"<div class='directive-region-item-container'>" +
"<div class='directive-region-label'>" +
"<div>" + kind + "</div>" +
"</div>" +
"<div class='directive-region-item'>" +
"<p>" + beginend + "<b>:</b>" + kind + "</p>" +
//(parameters ? "(" + parameters + ")" : "") +
"</div>"+
"</div>" +
"</div>\n";//# c.f. page_compiler_service.rb
if (parameters.replace(/^\s+|\s+$/g, "")) {
parameters = `(${parameters})`;
}
wrapped = `<b>${beginend}</b>:<b>` +
kind + "</b>" + parameters;// + "<br/>"
return wrapped;
});
return expanded;
}
}
EditingDirectives.initClass();
window.EditingDirectives = EditingDirectives;
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
# h/t http://brandonhilkert.com/blog/page-specific-javascript-in-rails/
$(".enote").ready ->
url = $(this).data('pdf-url')
console.log "loading PDF at URL " + url
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment