works_cited 0.1.14 → 0.1.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +27 -2
- data/VERSION +1 -1
- data/app/assets/javascripts/works_cited/addFields.js +44 -0
- data/app/assets/javascripts/works_cited/application.js +6 -1
- data/app/assets/javascripts/works_cited/loadPreview.js +45 -0
- data/app/assets/javascripts/works_cited/removeFields.js +37 -0
- data/app/assets/javascripts/works_cited/shared.js +59 -0
- data/app/assets/javascripts/works_cited/showFields.js +9 -0
- data/app/assets/javascripts/works_cited/submitForm.js +33 -0
- data/app/controllers/concerns/works_cited/params.rb +24 -0
- data/app/controllers/works_cited/citations_controller.rb +26 -13
- data/app/helpers/works_cited/application_helper.rb +46 -1
- data/app/models/works_cited/citation.rb +30 -34
- data/app/models/works_cited/contributor.rb +11 -28
- data/app/views/works_cited/citation_types/fields/_book.html.haml +1 -1
- data/app/views/works_cited/citation_types/fields/_electronic.html.haml +1 -1
- data/app/views/works_cited/citation_types/fields/_email.html.haml +1 -1
- data/app/views/works_cited/citation_types/fields/_interview.html.haml +1 -1
- data/app/views/works_cited/citation_types/fields/_periodical.html.haml +1 -1
- data/app/views/works_cited/citation_types/fields/_tweet.html.haml +1 -1
- data/app/views/works_cited/citations/_citation_fields.html.haml +28 -0
- data/app/views/works_cited/citations/_fields.html.haml +14 -0
- data/app/views/works_cited/citations/_form.html.haml +16 -53
- data/app/views/works_cited/citations/preview.html.haml +1 -1
- data/app/views/works_cited/contributors/_contributor_fields.html.haml +11 -0
- data/config/routes.rb +8 -2
- data/db/migrate/20210915160902_add_index_to_works_cited_contributors.rb +5 -1
- data/lib/works_cited/mixins/has_works_cited.rb +14 -1
- data/spec/dummy/app/controllers/doodads_controller.rb +2 -1
- data/spec/dummy/app/controllers/things_controller.rb +1 -1
- data/spec/dummy/app/views/doodads/_form.html.haml +2 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20210915161011_create_works_cited_citations.works_cited.rb +1 -0
- data/spec/dummy/db/migrate/20210915161012_create_works_cited_contributors.works_cited.rb +1 -0
- data/spec/dummy/db/migrate/20210915161013_add_index_to_works_cited_contributors.works_cited.rb +4 -1
- data/spec/dummy/db/schema.rb +60 -59
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +26893 -15
- data/spec/dummy/log/test.log +24838 -0
- data/spec/factories/things.rb +8 -0
- data/spec/models/doodad_spec.rb +13 -1
- data/spec/models/thing_spec.rb +13 -1
- data/spec/models/works_cited/citation_spec.rb +12 -0
- data/spec/requests/works_cited/citations_spec.rb +10 -8
- data/spec/routing/works_cited/citations_routing_spec.rb +5 -1
- data/works_cited.gemspec +14 -4
- metadata +13 -3
- data/app/views/works_cited/contributors/_fields.html.haml +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5db67f2a964c0433ad04e7e3fba13605d8bd38cf6d743a56f3eebf71e08a016d
|
4
|
+
data.tar.gz: f61b70f80049f28cbb897e2e4c9b1d107237838460266a71ae0c54b318d80cc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bab946554a8189670c7a8b5de7b15cd208ddc0df3c0d7539ad8200324cb2091e5b44452a3b3380b692e7a720f142cd4cec4b3c1a41b6118bb7630469f3be4d4f
|
7
|
+
data.tar.gz: 6989928293676bce8baf3332ad34c837394cb51e0b096e2997b980d8a54e51801536e259b355548f1a8a22fdc94338bdc9b22c7ee19212a47bca5f91127edd71
|
data/README.md
CHANGED
@@ -3,8 +3,6 @@ Works Cited allows you to add a list of the works cited in ActiveRecord objects,
|
|
3
3
|
|
4
4
|
Works Cited uses CanCanCan to authorize the editing of citations. This makes it easy for you to control access.
|
5
5
|
|
6
|
-
Works Cited is compatible with, but does not require, Rails Admin.
|
7
|
-
|
8
6
|
## Installation
|
9
7
|
Add this line to your application's Gemfile:
|
10
8
|
|
@@ -85,6 +83,33 @@ Add the helpers to the relevant views
|
|
85
83
|
= works_cited_list @record
|
86
84
|
```
|
87
85
|
|
86
|
+
To add routes so that you can edit citations on their own pages:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
mount WorksCited::Engine => '/works_cited'
|
90
|
+
```
|
91
|
+
|
92
|
+
To add the fields, nested inside your forms
|
93
|
+
|
94
|
+
```haml
|
95
|
+
= form_for(@doodad) do |f|
|
96
|
+
= works_cited_citations_fields f
|
97
|
+
```
|
98
|
+
|
99
|
+
Don't forget to add the controller concern to enable nested attributes with strong parameters
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
class RecipesController < ApplicationController
|
103
|
+
include Cookbook::Params
|
104
|
+
|
105
|
+
#...
|
106
|
+
|
107
|
+
def recipe_params
|
108
|
+
params.require(:doodad).permit(:name, :description, works_cited_params)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
88
113
|
## Contributing
|
89
114
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
90
115
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.15
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class addFields {
|
2
|
+
// This executes when the function is instantiated.
|
3
|
+
constructor() {
|
4
|
+
this.links = document.querySelectorAll('.add_fields')
|
5
|
+
this.iterateLinks()
|
6
|
+
const event = new Event('works_cited:load')
|
7
|
+
window.dispatchEvent(event);
|
8
|
+
}
|
9
|
+
|
10
|
+
iterateLinks() {
|
11
|
+
// If there are no links on the page, stop the function from executing.
|
12
|
+
if (this.links.length === 0) return
|
13
|
+
// Loop over each link on the page. A page could have multiple nested forms.
|
14
|
+
this.links.forEach(link => {
|
15
|
+
link.addEventListener('click', e => {
|
16
|
+
this.handleClick(link, e)
|
17
|
+
})
|
18
|
+
})
|
19
|
+
}
|
20
|
+
|
21
|
+
handleClick(link, e) {
|
22
|
+
// Stop the function from executing if a link or event were not passed into the function.
|
23
|
+
if (!link || !e) return
|
24
|
+
// Prevent the browser from following the URL.
|
25
|
+
e.preventDefault()
|
26
|
+
// Save a unique timestamp to ensure the key of the associated array is unique.
|
27
|
+
let time = new Date().getTime()
|
28
|
+
// Save the data id attribute into a variable. This corresponds to `new_object.object_id`.
|
29
|
+
let linkId = link.dataset.id
|
30
|
+
// Create a new regular expression needed to find any instance of the `new_object.object_id` used in the fields data attribute if there's a value in `linkId`.
|
31
|
+
let regexp = linkId ? new RegExp(linkId, 'g') : null
|
32
|
+
// Replace all instances of the `new_object.object_id` with `time`, and save markup into a variable if there's a value in `regexp`.
|
33
|
+
let newFields = regexp ? link.dataset.fields.replace(regexp, time) : null
|
34
|
+
// Add the new markup to the form if there are fields to add.
|
35
|
+
newFields ? link.insertAdjacentHTML('beforebegin', newFields) : null
|
36
|
+
// Tell it new fields are there
|
37
|
+
const event = new Event('works_cited:add_fields')
|
38
|
+
window.dispatchEvent(event);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
// Wait for turbolinks to load, otherwise `document.querySelectorAll()` won't work
|
43
|
+
window.addEventListener('load', () => new addFields())
|
44
|
+
window.addEventListener('works_cited:add_fields', () => new addFields())
|
@@ -0,0 +1,45 @@
|
|
1
|
+
function startPreview(url, id, citationLocator) {
|
2
|
+
var element = document.getElementById(id)
|
3
|
+
var fields = element.querySelectorAll('input, select');
|
4
|
+
var index = 0;
|
5
|
+
|
6
|
+
for (index = 0; index < fields.length; ++index) {
|
7
|
+
var textField = fields[index];
|
8
|
+
textField.addEventListener('change', (e) => loadPreview(url, citationLocator))
|
9
|
+
}
|
10
|
+
loadPreview(url, citationLocator)
|
11
|
+
}
|
12
|
+
|
13
|
+
function loadPreview(url, locator) {
|
14
|
+
// Build formData object.
|
15
|
+
var formData = new FormData(document.querySelector('form'))
|
16
|
+
var data = {}
|
17
|
+
data.citation = getFieldByLocator(formData, locator)
|
18
|
+
var authenticity_token = document.head.querySelector('meta[name="csrf-token"]').content
|
19
|
+
var preview = document.getElementById('preview')
|
20
|
+
var container = document.getElementById('preview-html')
|
21
|
+
|
22
|
+
fetch(url + '.json',
|
23
|
+
{
|
24
|
+
method: 'post',
|
25
|
+
headers: {
|
26
|
+
'X-Requested-With': 'XMLHttpRequest',
|
27
|
+
'X-CSRF-Token': authenticity_token,
|
28
|
+
'Content-type': 'application/json'
|
29
|
+
},
|
30
|
+
body: JSON.stringify(data),
|
31
|
+
credentials: 'same-origin'
|
32
|
+
})
|
33
|
+
.then(function(response) {
|
34
|
+
// When the page is loaded convert it to json
|
35
|
+
return response.json()
|
36
|
+
})
|
37
|
+
.then(function(json) {
|
38
|
+
// get the html value for it
|
39
|
+
return json.html
|
40
|
+
})
|
41
|
+
.then((html) => {
|
42
|
+
preview.style.display = 'block'
|
43
|
+
container.innerHTML = html
|
44
|
+
})
|
45
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class removeFields {
|
2
|
+
// This executes when the function is instantiated.
|
3
|
+
constructor() {
|
4
|
+
this.iterateLinks()
|
5
|
+
}
|
6
|
+
|
7
|
+
iterateLinks() {
|
8
|
+
document.querySelectorAll('.remove_fields').forEach(link => {
|
9
|
+
link.addEventListener('click', e => {
|
10
|
+
this.handleClick(link, e)
|
11
|
+
})
|
12
|
+
})
|
13
|
+
}
|
14
|
+
|
15
|
+
handleClick(link, e) {
|
16
|
+
// Stop the function from executing if a link or event were not passed into the function.
|
17
|
+
if (!link || !e) return
|
18
|
+
// Prevent the browser from following the URL.
|
19
|
+
e.preventDefault()
|
20
|
+
// Find the parent wrapper for the set of nested fields.
|
21
|
+
let fieldParent = link.closest('.nested-fields')
|
22
|
+
// If there is a parent wrapper, find the hidden delete field.
|
23
|
+
let deleteField = fieldParent
|
24
|
+
? fieldParent.querySelector('input[type="hidden"]')
|
25
|
+
: null
|
26
|
+
// If there is a delete field, update the value to `1` and hide the corresponding nested fields.
|
27
|
+
if (deleteField) {
|
28
|
+
deleteField.value = 1
|
29
|
+
fieldParent.style.display = 'none'
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
// Wait for turbolinks to load, otherwise `document.querySelectorAll()` won't work
|
35
|
+
window.addEventListener('load', () => new removeFields())
|
36
|
+
window.addEventListener('works_cited:load', () => new removeFields())
|
37
|
+
window.addEventListener('works_cited:add_fields', () => new removeFields())
|
@@ -0,0 +1,59 @@
|
|
1
|
+
function packInContainer(container, keyParts, value) {
|
2
|
+
var part = keyParts.shift()
|
3
|
+
var packed = value
|
4
|
+
if(keyParts.length > 0) {
|
5
|
+
var previous = container[part] || {}
|
6
|
+
packed = packInContainer(previous, keyParts, value)
|
7
|
+
}
|
8
|
+
|
9
|
+
return {...container, [part]: packed}
|
10
|
+
}
|
11
|
+
|
12
|
+
function getWritableField(key) {
|
13
|
+
var fields = document.getElementsByName(key)
|
14
|
+
var found
|
15
|
+
|
16
|
+
fields.forEach((field) => {
|
17
|
+
if(field.readonly === undefined || !field.readonly) {
|
18
|
+
if(field.value) {
|
19
|
+
found = field.value
|
20
|
+
return
|
21
|
+
}
|
22
|
+
}
|
23
|
+
})
|
24
|
+
|
25
|
+
return found || ''
|
26
|
+
}
|
27
|
+
|
28
|
+
function getFieldByLocator(data, locator) {
|
29
|
+
var regex = /^\[(.*)\]$/
|
30
|
+
var container = {}
|
31
|
+
|
32
|
+
// Display the key/value pairs
|
33
|
+
for(var pair of data.entries()) {
|
34
|
+
// We want to avoid using pair[1] even though it's there because some fields are readonly
|
35
|
+
var key = pair[0]
|
36
|
+
if(key.startsWith(locator)) {
|
37
|
+
var strippedKeyString = key.replace(locator, '').replace(regex, "$1")
|
38
|
+
var keyParts = strippedKeyString.split('][')
|
39
|
+
var value = getWritableField(key)
|
40
|
+
console.warn(strippedKeyString)
|
41
|
+
container = packInContainer(container, keyParts, value)
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
return container
|
46
|
+
}
|
47
|
+
|
48
|
+
function getFormDataPacked(data) {
|
49
|
+
var container = {}
|
50
|
+
|
51
|
+
// Display the key/value pairs
|
52
|
+
for(var pair of data.entries()) {
|
53
|
+
// We want to avoid using pair[1] even though it's there because some fields are readonly
|
54
|
+
var key = pair[0]
|
55
|
+
container[key] = getWritableField(key)
|
56
|
+
}
|
57
|
+
|
58
|
+
return container
|
59
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
function showFields(citation_id, citation_type) {
|
2
|
+
citation = document.getElementById(`citation-${citation_id}`)
|
3
|
+
for (let element of citation.getElementsByClassName('fields')){
|
4
|
+
element.style.display='none'
|
5
|
+
element.readonly=true
|
6
|
+
}
|
7
|
+
console.warn(citation.getElementsByClassName(`fields-${citation_type}`)[0])
|
8
|
+
citation.getElementsByClassName(`fields-${citation_type}`)[0].style.display = 'block'
|
9
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
function post(path, params, method = 'post') {
|
2
|
+
|
3
|
+
// The rest of this code assumes you are not using a library.
|
4
|
+
// It can be made less verbose if you use one.
|
5
|
+
const form = document.createElement('form')
|
6
|
+
form.method = method
|
7
|
+
form.action = path
|
8
|
+
|
9
|
+
for (const key in params) {
|
10
|
+
if (params.hasOwnProperty(key)) {
|
11
|
+
const hiddenField = document.createElement('input')
|
12
|
+
hiddenField.type = 'hidden'
|
13
|
+
hiddenField.name = key
|
14
|
+
hiddenField.value = params[key]
|
15
|
+
|
16
|
+
form.appendChild(hiddenField)
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
document.body.appendChild(form)
|
21
|
+
form.submit()
|
22
|
+
}
|
23
|
+
|
24
|
+
function addFilterToSubmit() {
|
25
|
+
var form = document.querySelector('form')
|
26
|
+
|
27
|
+
form.onsubmit = function (e) {
|
28
|
+
e.preventDefault()
|
29
|
+
var formData = new FormData(form)
|
30
|
+
var revisedData = getFormDataPacked(formData)
|
31
|
+
return post(form.action, revisedData)
|
32
|
+
}
|
33
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WorksCited
|
4
|
+
# Concern to allow strong parameters when using accepts_nested_attributes_for to edit Cookbook Uses
|
5
|
+
module Params
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
# rescue_from Pundit::NotAuthorizedError, with: :authorization_error
|
10
|
+
# include Pundit
|
11
|
+
end
|
12
|
+
|
13
|
+
def works_cited_params
|
14
|
+
{
|
15
|
+
works_cited_citations_attributes: [
|
16
|
+
:id, :citation_type, :title, :container_title, :publisher, :city, :edition, :volume,
|
17
|
+
:number, :series, :year, :record, :media, :url, :pages, :published_at, :online_database, :doi,
|
18
|
+
:accessed_at, :_destroy,
|
19
|
+
{ works_cited_contributors_attributes: %i[id contributor_role first middle last suffix handle _destroy] }
|
20
|
+
]
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -16,11 +16,10 @@ module WorksCited
|
|
16
16
|
|
17
17
|
# GET /citations/preview
|
18
18
|
def preview
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
render :preview, layout: false
|
19
|
+
authorize! :preview, Citation
|
20
|
+
locals = { citation: citation_for_preview, contributors: contributors_for_preview }
|
21
|
+
html = render_to_string('preview', formats: [:html], layout: false, locals: locals)
|
22
|
+
render json: { html: html }
|
24
23
|
end
|
25
24
|
|
26
25
|
# GET /citations/new
|
@@ -55,9 +54,15 @@ module WorksCited
|
|
55
54
|
|
56
55
|
private
|
57
56
|
|
58
|
-
def
|
57
|
+
def citation_for_preview
|
58
|
+
return Citation.new(preview_params) unless params[:id].present?
|
59
|
+
|
60
|
+
Citation.find(params[:id]).assign_attributes(preview_params)
|
61
|
+
end
|
62
|
+
|
63
|
+
def contributors_for_preview
|
59
64
|
contributors_array = []
|
60
|
-
raw_contributors =
|
65
|
+
raw_contributors = preview_params[:works_cited_contributors_attributes]
|
61
66
|
raw_contributors&.each do |_index, contributor|
|
62
67
|
destroy = contributor.delete(:_destroy)
|
63
68
|
next if destroy == '1'
|
@@ -92,12 +97,8 @@ module WorksCited
|
|
92
97
|
|
93
98
|
def model_option_group(items, model)
|
94
99
|
model_name = model.model_name
|
95
|
-
|
96
|
-
|
97
|
-
record_type: model_name,
|
98
|
-
records: items.map { |item| item_option(item, model_name) }
|
99
|
-
}
|
100
|
-
)
|
100
|
+
records = items.map { |item| item_option(item, model_name) }
|
101
|
+
OpenStruct.new({ record_type: model_name, records: records })
|
101
102
|
end
|
102
103
|
|
103
104
|
def set_records
|
@@ -109,6 +110,18 @@ module WorksCited
|
|
109
110
|
end.compact
|
110
111
|
end
|
111
112
|
|
113
|
+
# Only allow a list of trusted parameters through.
|
114
|
+
def preview_params
|
115
|
+
json = JSON.parse(request.raw_post)
|
116
|
+
json_params = ActionController::Parameters.new(json)
|
117
|
+
json_params.require(:citation).permit(
|
118
|
+
:id, :citation_type, :title, :container_title, :publisher, :city, :edition, :volume,
|
119
|
+
:number, :series, :year, :record, :media, :url, :pages, :published_at, :online_database, :doi,
|
120
|
+
:accessed_at,
|
121
|
+
works_cited_contributors_attributes: %i[id contributor_role first middle last suffix handle _destroy]
|
122
|
+
)
|
123
|
+
end
|
124
|
+
|
112
125
|
# Only allow a list of trusted parameters through.
|
113
126
|
def citation_params
|
114
127
|
params.require(:citation).permit(
|
@@ -18,11 +18,15 @@ module WorksCited
|
|
18
18
|
render path, citation: citation, contributors: contributors
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def works_cited_type_fields(form, citation_type)
|
22
22
|
path = partial_path_or_default('fields', citation_type)
|
23
23
|
render path, f: form, citation_type: citation_type
|
24
24
|
end
|
25
25
|
|
26
|
+
def works_cited_citations_fields(form)
|
27
|
+
render 'works_cited/citations/fields', form: form
|
28
|
+
end
|
29
|
+
|
26
30
|
def list_names(names)
|
27
31
|
first = first_contributor(names)
|
28
32
|
first_contributor_name = first&.full_name(first.name_order)
|
@@ -36,6 +40,47 @@ module WorksCited
|
|
36
40
|
end
|
37
41
|
end
|
38
42
|
|
43
|
+
# This method creates a link with `data-id` `data-fields` attributes. These attributes are
|
44
|
+
# used to create new instances of the nested fields through Javascript.
|
45
|
+
def works_cited_link_to_add_fields(name, form, association, partial = nil)
|
46
|
+
# Takes an object (@person) and creates a new instance of its associated model (:addresses)
|
47
|
+
# To better understand, run the following in your terminal:
|
48
|
+
# rails c --sandbox
|
49
|
+
# @person = Person.new
|
50
|
+
# new_object = @person.send(:addresses).klass.new
|
51
|
+
new_object = form.object.send(association).klass.new
|
52
|
+
|
53
|
+
# Saves the unique ID of the object into a variable.
|
54
|
+
# This is needed to ensure the key of the associated array is unique. This is makes
|
55
|
+
# parsing the content in the `data-fields` attribute easier through Javascript.
|
56
|
+
# We could use another method to achive this.
|
57
|
+
id = new_object.object_id
|
58
|
+
|
59
|
+
# https://api.rubyonrails.org/ fields_for(record_name, record_object = nil, fields_options = {}, &block)
|
60
|
+
# record_name = :addresses
|
61
|
+
# record_object = new_object
|
62
|
+
# fields_options = { child_index: id }
|
63
|
+
# child_index` is used to ensure the key of the associated array is unique, and that it
|
64
|
+
# matched the value in the `data-id` attribute.
|
65
|
+
# `person[addresses_attributes][child_index_value][_destroy]`
|
66
|
+
fields = form.simple_fields_for(association, new_object, child_index: id) do |builder|
|
67
|
+
# `association.to_s.singularize + "_fields"` ends up evaluating to `address_fields`
|
68
|
+
# The render function will then look for `views/people/_address_fields.html.erb`
|
69
|
+
# The render function also needs to be passed the value of 'builder', because
|
70
|
+
# `views/people/_address_fields.html.erb` needs this to render the form tags.
|
71
|
+
partial ||= "#{association.to_s.singularize}_fields"
|
72
|
+
render(partial, record: new_object, f: builder)
|
73
|
+
end
|
74
|
+
|
75
|
+
# This renders a simple link, but passes information into `data` attributes.
|
76
|
+
# This info can be named anything we want, but in this case we chose `data-id:` and `data-fields:`.
|
77
|
+
# The `id:` is from `new_object.object_id`.
|
78
|
+
# The `fields:` are rendered from the `fields` blocks.
|
79
|
+
# We use `gsub("\n", "")` to remove anywhite space from the rendered partial.
|
80
|
+
# The `id:` value needs to match the value used in `child_index: id`.
|
81
|
+
link_to(name, '#', class: 'add_fields button button-action', data: { id: id, fields: fields.gsub("\n", '') })
|
82
|
+
end
|
83
|
+
|
39
84
|
private
|
40
85
|
|
41
86
|
def partial_path_or_default(purpose, citation_type)
|
@@ -17,20 +17,24 @@ module WorksCited
|
|
17
17
|
|
18
18
|
# Relationships
|
19
19
|
belongs_to :record, polymorphic: true
|
20
|
-
has_many :works_cited_contributors,
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
has_many :works_cited_contributors, lambda {
|
21
|
+
order(:last, :first, :middle, :suffix, :handle)
|
22
|
+
}, inverse_of: :works_cited_citation, class_name: 'WorksCited::Contributor',
|
23
|
+
foreign_key: :works_cited_citation_id, dependent: :destroy
|
24
|
+
has_many :works_cited_authors, lambda {
|
25
|
+
authors.order(:last, :first, :middle, :suffix, :handle)
|
26
|
+
}, inverse_of: :works_cited_citation, class_name: 'WorksCited::Contributor',
|
27
|
+
foreign_key: :works_cited_citation_id
|
28
|
+
accepts_nested_attributes_for :works_cited_contributors, reject_if: :all_blank, allow_destroy: true
|
25
29
|
|
26
30
|
# Scopes
|
27
31
|
scope :ordered_by_author, (lambda do
|
28
32
|
joins(:works_cited_authors)
|
29
33
|
.order('MIN(works_cited_contributors.last) ASC, '\
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
'MIN(works_cited_contributors.first) ASC, '\
|
35
|
+
'MIN(works_cited_contributors.middle) ASC, '\
|
36
|
+
'MIN(works_cited_contributors.suffix) ASC, '\
|
37
|
+
'MIN(works_cited_contributors.handle) ASC')
|
34
38
|
.group(:id)
|
35
39
|
end)
|
36
40
|
|
@@ -44,17 +48,31 @@ module WorksCited
|
|
44
48
|
end
|
45
49
|
|
46
50
|
# Instance Methods
|
51
|
+
def works_cited_contributors_attributes=(raw_contributors)
|
52
|
+
array = []
|
53
|
+
raw_contributors&.each do |_index, contributor|
|
54
|
+
destroy = contributor.delete(:_destroy)
|
55
|
+
if destroy == '1'
|
56
|
+
Contributor.find(contributor[:id]).destroy if contributor[:id]
|
57
|
+
next
|
58
|
+
end
|
59
|
+
|
60
|
+
array << contributor
|
61
|
+
end
|
62
|
+
super array
|
63
|
+
end
|
64
|
+
|
47
65
|
def record=(value)
|
48
66
|
unless value.is_a? String
|
49
67
|
super(value)
|
50
68
|
return
|
51
69
|
end
|
52
70
|
|
53
|
-
model_name,
|
54
|
-
return unless model_name.present? &&
|
71
|
+
model_name, my_id = value.split(':')
|
72
|
+
return unless model_name.present? && my_id.present?
|
55
73
|
|
56
74
|
model = model_name.constantize
|
57
|
-
super model.find(
|
75
|
+
super model.find(my_id)
|
58
76
|
end
|
59
77
|
|
60
78
|
def periodical?
|
@@ -76,27 +94,5 @@ module WorksCited
|
|
76
94
|
def email?
|
77
95
|
citation_type == 'email'
|
78
96
|
end
|
79
|
-
|
80
|
-
if defined?(RailsAdmin)
|
81
|
-
rails_admin do
|
82
|
-
visible false
|
83
|
-
edit do
|
84
|
-
field :citation_type, :enum do
|
85
|
-
enum do
|
86
|
-
WorksCited.configuration.valid_citation_types
|
87
|
-
end
|
88
|
-
end
|
89
|
-
field :works_cited_contributors do
|
90
|
-
label 'Contributors'
|
91
|
-
end
|
92
|
-
include_all_fields
|
93
|
-
field :media
|
94
|
-
field :record do
|
95
|
-
# Can't remove this using :inverse_of because it's polymorphic
|
96
|
-
visible false
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
97
|
end
|
102
98
|
end
|
@@ -57,28 +57,25 @@ module WorksCited
|
|
57
57
|
|
58
58
|
def name_parts
|
59
59
|
parts = []
|
60
|
-
parts << first_name_or_initial
|
61
|
-
parts << middle_initial
|
62
|
-
parts <<
|
63
|
-
"#{last}, #{suffix}"
|
64
|
-
else
|
65
|
-
suffix.presence || last.presence
|
66
|
-
end
|
60
|
+
parts << first_name_or_initial.presence
|
61
|
+
parts << middle_initial.presence
|
62
|
+
parts << [last.presence, suffix.presence].compact.join(', ').presence
|
67
63
|
parts
|
68
64
|
end
|
69
65
|
|
70
66
|
def name_parts_reversed
|
71
67
|
parts = []
|
72
|
-
parts <<
|
73
|
-
parts << first_name_or_initial
|
74
|
-
parts << if suffix.present? && middle_initial.present?
|
75
|
-
"#{middle_initial}, #{suffix}"
|
76
|
-
else
|
77
|
-
suffix.presence || middle_initial
|
78
|
-
end
|
68
|
+
parts << [last.presence, name_other_parts.presence].compact.join(', ').presence
|
79
69
|
parts
|
80
70
|
end
|
81
71
|
|
72
|
+
def name_other_parts
|
73
|
+
other_parts = []
|
74
|
+
other_parts << first_name_or_initial.presence
|
75
|
+
other_parts << [middle_initial.presence, suffix.presence].compact.join(', ').presence
|
76
|
+
other_parts.compact.join(' ').presence
|
77
|
+
end
|
78
|
+
|
82
79
|
def first_name_or_initial
|
83
80
|
return nil unless first.present?
|
84
81
|
|
@@ -90,19 +87,5 @@ module WorksCited
|
|
90
87
|
|
91
88
|
middle[0, 1]&.upcase
|
92
89
|
end
|
93
|
-
|
94
|
-
if defined?(RailsAdmin)
|
95
|
-
rails_admin do
|
96
|
-
visible false
|
97
|
-
edit do
|
98
|
-
field :contributor_role, :enum do
|
99
|
-
enum do
|
100
|
-
WorksCited.configuration.valid_contributor_roles
|
101
|
-
end
|
102
|
-
end
|
103
|
-
include_all_fields
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
90
|
end
|
108
91
|
end
|
@@ -10,5 +10,5 @@
|
|
10
10
|
= f.input :pages, hint: 'like "pp. 150-53"'
|
11
11
|
= f.input :online_database
|
12
12
|
= f.input :doi, label: 'Digital Object Identifier'
|
13
|
-
= f.input :url
|
13
|
+
= f.input :url, as: :string
|
14
14
|
= f.input :accessed_at, as: :string, hint: 'Will be parsed and formatted', input_html: { value: f.object.accessed_at&.mla_date }
|
@@ -1,4 +1,4 @@
|
|
1
1
|
= f.input :title, label: 'Tweet text'
|
2
2
|
= f.input :publisher, default: 'Twitter'
|
3
3
|
= f.input :published_at, label: 'Time posted', as: :string, hint: 'Will be parsed and formatted', input_html: { value: f.object.published_at&.mla_datetime }
|
4
|
-
= f.input :url
|
4
|
+
= f.input :url, as: :string
|