works_cited 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|