Whole lotta love.

This commit is contained in:
Dan Chadwick 2024-11-18 18:44:53 -05:00
parent 40324ae10b
commit 13971f626c
80 changed files with 2746 additions and 1495 deletions

View File

@ -25,6 +25,7 @@
"drupal/core-project-message": "^10.3",
"drupal/core-recommended": "^10.3",
"drupal/devel_entity_updates": "^4.1",
"drupal/entity_clone": "^2.1@beta",
"drupal/entity_hierarchy": "^3.3",
"drupal/field_group": "^3.4",
"drupal/flexslider": "^3.0@alpha",
@ -35,6 +36,7 @@
"drupal/html_formatter": "^2.0",
"drupal/jsonapi_permission_access": "1.0.1",
"drupal/layout_builder_admin_theme": "^2.0",
"drupal/layout_builder_block_clone": "^1.3",
"drupal/layout_builder_styles": "^2.0",
"drupal/mailsystem": "^4.5",
"drupal/migrate_plus": "^6.0",
@ -45,6 +47,7 @@
"drupal/stage_file_proxy": "^3.1",
"drupal/views_bulk_operations": "^4.2",
"drupal/views_json_source": "^2.0",
"drupal/workflow": "^1.8",
"drush/drush": "^12.4",
"symfony/css-selector": "^6",
"symfony/dom-crawler": "^6",
@ -65,7 +68,8 @@
"phpstan/extension-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"php-http/discovery": true,
"cweagans/composer-patches": true
"cweagans/composer-patches": true,
"tbachert/spi": true
},
"sort-packages": true
},

1424
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
name: Auto Alt
description: 'AI generated alt text on media items.'
type: module
package: Media
core_version_requirement: ^10 || ^11

View File

@ -1,9 +0,0 @@
auto_alt:
css:
theme:
css/styles.css: { }
js:
js/autoAlt.js: { }
dependencies:
- core/once
- core/drupal

View File

@ -1,12 +0,0 @@
<?php
function auto_alt_form_alter(&$form, &$form_state, $form_id) {
if ($form_id !== 'media_image_edit_form') {
return;
}
$form['auto_alt'] = [
'#type' => 'markup',
'#markup' => '<span id="autoalt">🪄</span>'
];
$form['#attached']['library'][] = 'auto_alt/auto_alt';
}

View File

@ -1,7 +0,0 @@
auto_alt.content:
path: '/ai/alt-text-generator'
defaults:
_controller: '\Drupal\auto_alt\Controller\AltTextGenerator::generate'
_title: ''
requirements:
_permission: 'access content'

View File

@ -1,19 +0,0 @@
.form-managed-file__meta-items {
position: relative;
}
#autoalt {
position: absolute;
top: 46px;
right: 3px;
padding: 8px;
background: 'whitesmoke';
transform: translateY(-50%);
border-radius: 0 5px 5px 0;
cursor: pointer;
transition: background .2s ease;
}
#autoalt:hover {
background: lightgray;
}

View File

@ -1,32 +0,0 @@
(function (Drupal, once) {
Drupal.behaviors.autoAltBehavior = {
attach: function (context, settings) {
once('autoAltBehavior', '#edit-field-media-image-0-alt', context).forEach(function (element) {
// Move the wand to where it should be.
let wand = context.getElementById("autoalt");
let mediaMeta = context.querySelector('.form-managed-file__meta-items');
mediaMeta.append(wand);
wand.addEventListener('click', generateAltText);
});
async function generateAltText() {
let imageUrl = document.querySelector('.image-preview__img-wrapper img').src;
const url = "/ai/alt-text-generator?";
try {
const response = await fetch(url + new URLSearchParams({
image: imageUrl,
}).toString());
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const json = await response.json();
let altInput = document.getElementById('edit-field-media-image-0-alt');
altInput.value = json;
} catch (error) {
console.error(error.message);
}
}
}
};
})(Drupal, once);

View File

@ -1,41 +0,0 @@
<?php
namespace Drupal\auto_alt\Controller;
use \Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Generate Alt Text for an image.
*/
class AltTextGenerator extends ControllerBase {
/**
* Returns a renderable array for a test page.
*
* return []
*/
public function generate() {
// Get the url to the image from the request.
$image_url = \Drupal::request()->get('image');
if (!$image_url) {
throw new NotFoundHttpException();
}
$image_contents = file_get_contents($image_url);
$encoded_image = base64_encode($image_contents);
// Now send this to chat gpt / ai to generate alt text.
$response = [
"This is some alt text for an image.",
"A new string for alt text",
"Another string about alt text",
];
$text_to_return = rand(0,2);
$json_response = new JsonResponse();
$json_response->headers->set('Content-Type', 'application/json');
$json_response->setData($response[$text_to_return]);
return $json_response;
}
}

View File

@ -0,0 +1,24 @@
#block-gin-content .admin-item {
margin: 20px;
padding: 5px;
}
#block-gin-content .admin-item__description {
margin: 0;
}
.draggable .tabledrag-cell {
padding: 5px 0;
}
.entity-form-display-form {
font-size: 12px;
}
.entity-form-display-form select, .field-plugin-summary {
font-size: 12px !important;
}
.layout-builder-block {
padding: 0 !important;
}

View File

@ -0,0 +1,5 @@
name: DC Core
description: 'Core functionality and overrides.'
package: DC Core
type: module
core_version_requirement: ^10 || ^11

View File

@ -0,0 +1,4 @@
admin_overrides:
css:
theme:
css/admin.css: { }

View File

@ -0,0 +1,10 @@
<?php
function dc_core_page_attachments(array &$attachments) {
if (\Drupal::service('router.admin_context')->isAdminRoute()) {
$attachments['#attached']['library'][] = 'dc_core/admin_overrides';
}
if (\Drupal::routeMatch()->getRouteObject()->getOption('_layout_builder')) {
$attachments['#attached']['library'][] = 'dc_core/admin_overrides';
}
}

View File

@ -0,0 +1,21 @@
name: Accordion
props:
type: object
properties:
accordionType:
type: string
title: Accordion Type
description: 'The type of accordion.'
slots:
headline:
title: Headline
required: true
description: This is the headline for an accordion.
body:
title: Body
required: true
description: This is the body for an accordion.
libraryOverrides:
dependencies:
- core/drupal
- core/once

View File

@ -0,0 +1 @@
@charset "UTF-8";.ib-accordion__item{margin:10px 0;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.ib-accordion__headline{background-color:#efefef;padding:20px;cursor:pointer;display:flex;align-items:center}.ib-accordion__headline h1,.ib-accordion__headline h2,.ib-accordion__headline h3,.ib-accordion__headline h4,.ib-accordion__headline h5,.ib-accordion__headline h6{font-size:18px!important;margin:0!important;color:#3272b3;--bs-heading-color:#3272b3}.ib-accordion__headline::before{display:flex;float:left;content:"+";font-size:30px;font-weight:700;justify-content:center;align-items:center;background-color:var(--site-primary);border-radius:100%;width:30px;height:30px;color:#fff;margin-right:10px}.ib-accordion__body{padding:0 20px;max-height:0;overflow:hidden;will-change:max-height;transition:.25s ease-out;align-items:center}.ib-accordion__body>:first-child{margin:20px 0}.ib-accordion__active .ib-accordion__headline::before{content:""}

View File

@ -0,0 +1,38 @@
(function (Drupal, once) {
Drupal.behaviors.accordion = {
attach(context) {
const accordions = once('accordion', '.ib-accordion__item', context);
if (!accordions) {
return;
}
const openAccordion = (accordion) => {
const content = accordion.querySelector(".ib-accordion__body");
accordion.classList.add("ib-accordion__active");
content.style.maxHeight = content.scrollHeight + "px";
content.setAttribute('aria-expanded', true);
}
const closeAccordion = (accordion) => {
const content = accordion.querySelector(".ib-accordion__body");
accordion.classList.remove("ib-accordion__active");
content.style.maxHeight = null;
content.setAttribute('aria-expanded', false)
}
accordions.forEach((accordion) => {
const headline = accordion.querySelector(".ib-accordion__headline");
const content = accordion.querySelector(".ib-accordion__body");
headline.onclick = () => {
if (content.style.maxHeight) {
closeAccordion(accordion);
} else {
accordions.forEach((accordion) => closeAccordion(accordion));
openAccordion(accordion);
}
};
});
}
};
}(Drupal, once));

View File

@ -0,0 +1,59 @@
.ib-accordion {
&__item {
margin: 10px 0;
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
}
&__headline {
background-color: #EFEFEF;
padding: 20px;
cursor: pointer;
display: flex;
align-items: center;
h1, h2, h3, h4, h5, h6 {
font-size: 18px !important;
margin: 0 !important;
color: #3272b3;
--bs-heading-color: #3272b3;
}
&::before {
display: flex;
float: left;
content: "\002B";
font-size: 30px;
font-weight: 700;
justify-content: center;
align-items: center;
background-color: var(--site-primary);
border-radius: 100%;
width: 30px;
height: 30px;
color: #fff;
margin-right: 10px;
}
}
&__body {
padding: 0 20px;
max-height: 0;
overflow: hidden;
will-change: max-height;
transition: all 0.25s ease-out;
align-items: center;
> :first-child {
margin: 20px 0;
}
}
&__active {
.ib-accordion__headline::before {
content: "\2212";
}
}
}

View File

@ -0,0 +1,14 @@
{% set classes = [
'ib-accordion__item',
'ib-accordion__' ~ type|clean_class,
] %}
{% set ariaLabel = 'accordion-' ~ random(0, 10000) %}
<div class="{{ classes|join(" ") }}">
<div class="ib-accordion__headline">
{% block headline %}{% endblock %}
</div>
<div class="ib-accordion__body" aria-labelledby="{{ ariaLabel }}" aria-expanded="false">
{% block body %}{% endblock %}
</div>
</div>

View File

@ -0,0 +1,21 @@
name: Banner
props:
type: object
properties:
bannerText:
type: string
title: Banner Text
description: This is the text for the banner.
bannerImage:
type: string
title: Banner Image
description: This is the image for the banner.
bannerType:
type: string
title: Banner Type
description: The type of banner.
enum: ["full", "thin"]
addClasses:
type: string
title: Additonal Classes
description: String of additional classses to add to the banner.

View File

@ -0,0 +1 @@
.banner__thin{height:192px}.banner__thin .banner__text{position:relative;top:50%;transform:translateY(-50%)}.banner-image{background-size:cover;background-position:center;background-repeat:no-repeat}

View File

@ -0,0 +1,21 @@
.banner {
&__thin {
height: 192px;
.banner__text {
position: relative;
top: 50%;
transform: translateY(-50%);
}
}
&-image {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
}

View File

@ -0,0 +1,16 @@
{%
set classes = [
'banner',
'banner__' ~ bannerType|clean_class,
]
%}
{% if addClasses %}
{% set classes = classes|merge([addClasses]) %}
{% endif %}
<div class="{{ classes|join(" ") }}" style="background-image: url({{ bannerImage }})">
<div class="banner__text container">
{{ include('lawa:heading', { level: '1', headingText: bannerText, color: textColor }, with_context = false) }}
</div>
</div>

View File

@ -0,0 +1,28 @@
name: Heading
props:
type: object
required:
- level
properties:
level:
type: string
title: Heading Level
description: 'The level of the heading, i.e. h1'
enum: ['1', '2', '3', '4', '5', '6']
headingText:
type: string
title: Heading Text
description: 'The text within the heading.'
color:
type: string
title: Heading Text Color
description: The color of the text for the heading.
enum: ['primary', 'secondary', 'white', 'black', 'neutral', 'teal']
addClasses:
type: string
title: Classes
description: Additional classes for the heading.
subheading:
type: string
title: Subheading
description: Text that can appear under the heading.

View File

@ -0,0 +1 @@
.heading{margin:0}.heading__primary{color:#1d53a3}.heading__white{color:#fff}.heading__black{color:#000}.heading__neutral{color:#121212}.heading__teal{color:#01b9b6}

View File

@ -0,0 +1,27 @@
@import 'src/sass/partials/variables';
.heading {
margin: 0;
&__primary {
color: $primary_navy;
}
&__white {
color: $white;
}
&__black {
color: $black;
}
&__neutral {
color: $neutrals;
}
&__teal {
color: $teal;
}
}

View File

@ -0,0 +1,22 @@
{% set classes = ['heading'] %}
{% if color %}
{% set classes = classes|merge(['heading__' ~ color|clean_class]) %}
{% endif %}
{% if addClasses %}
{% set classes = classes|merge([addClasses])%}
{% endif %}
{% set heading = headingText.0['#context']['value']|default(headingText) %}
{% if ariaLabel %}
<h{{ level }} id="{{ ariaLabel }}" class="{{ classes|join(" ")}}">{{ heading }}</h{{ level}}>
{% else %}
<h{{ level }} class="{{ classes|join(" ")}}">
{{ heading }}
{% if subheading %}
<br><em>{{ subheading }}</em>
{% endif %}
</h{{ level}}>
{% endif %}

View File

@ -1,2 +0,0 @@
.idea
.idea/

View File

@ -1,39 +0,0 @@
## JSON Ingestion Examples
The data is pulled from https://jsonplaceholder.typicode.com/
### Approach 1: Migrate API
#### Requirements
- Migrate
- Migrate Plus
There are 2 migrations:
- images (5000 total)
- json_posts_to_posts (100 total)
To run them you can do:
```
drush mim images --limit=100
drush mim json_posts_to_posts
```
there are only 100 posts, so for our purposes we don't need more than 100 images.
Once both migrations have been run you can see the data at `/migration-posts` which is controller by a view.
This approach has the advantage of requiring migrate API which pulls in a lot of helpful things,
such as rollbacks, update old items, only importing new items...etc.
### Approach 2: Custom Drush Command
Will import the first 100 images only, followed by 100 posts.
Also available for viewing on `/migration-posts` after import.
```
drush jie-posts
```
### Approach 3: Views JSON Source
#### Requirements
- views_json_source
Make sure `views_json_source` module is included in your codebase.
Then visit `/views-json-source` after enabling this module which is a preconfigured view of titles and body fields from external JSON.
### Approach 4: Javascript Only
1. Attach a javascript library to a page
2. Fetch the API data and render it all client side

View File

@ -1,16 +0,0 @@
{
"name": "drupal/json_ingestion_examples",
"type": "drupal-module",
"license": "GPL-2.0-or-later",
"authors": [
{
"name": "Dan Chadwick",
"email": "dan.chadwick@acquia.com"
}
],
"require": {
"drupal/core": "^9 || ^10",
"drupal/migrate_plus": "^6.0",
"drupal/views_json_source": "^1.4"
}
}

View File

@ -1,87 +0,0 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.post.body
- field.field.node.post.field_image
- image.style.thumbnail
- node.type.post
module:
- image
- path
- text
id: node.post.default
targetEntityType: node
bundle: post
mode: default
content:
body:
type: text_textarea_with_summary
weight: 121
region: content
settings:
rows: 9
summary_rows: 3
placeholder: ''
show_summary: false
third_party_settings: { }
created:
type: datetime_timestamp
weight: 10
region: content
settings: { }
third_party_settings: { }
field_image:
type: image_image
weight: 1
region: content
settings:
progress_indicator: throbber
preview_image_style: thumbnail
third_party_settings: { }
path:
type: path
weight: 30
region: content
settings: { }
third_party_settings: { }
promote:
type: boolean_checkbox
weight: 15
region: content
settings:
display_label: true
third_party_settings: { }
status:
type: boolean_checkbox
weight: 120
region: content
settings:
display_label: true
third_party_settings: { }
sticky:
type: boolean_checkbox
weight: 16
region: content
settings:
display_label: true
third_party_settings: { }
title:
type: string_textfield
weight: -5
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
uid:
type: entity_reference_autocomplete
weight: 5
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
hidden: { }

View File

@ -1,41 +0,0 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.post.body
- field.field.node.post.field_image
- image.style.wide
- node.type.post
module:
- image
- text
- user
id: node.post.default
targetEntityType: node
bundle: post
mode: default
content:
body:
type: text_default
label: hidden
settings: { }
third_party_settings: { }
weight: 101
region: content
field_image:
type: image
label: hidden
settings:
image_link: ''
image_style: wide
image_loading:
attribute: eager
third_party_settings: { }
weight: -1
region: content
links:
settings: { }
third_party_settings: { }
weight: 100
region: content
hidden: { }

View File

@ -1,43 +0,0 @@
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.node.teaser
- field.field.node.post.body
- field.field.node.post.field_image
- image.style.medium
- node.type.post
module:
- image
- text
- user
id: node.post.teaser
targetEntityType: node
bundle: post
mode: teaser
content:
body:
type: text_summary_or_trimmed
label: hidden
settings:
trim_length: 600
third_party_settings: { }
weight: 101
region: content
field_image:
type: image
label: hidden
settings:
image_link: content
image_style: medium
image_loading:
attribute: lazy
third_party_settings: { }
weight: -1
region: content
links:
settings: { }
third_party_settings: { }
weight: 100
region: content
hidden: { }

View File

@ -1,23 +0,0 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.body
- node.type.post
module:
- text
id: node.post.body
field_name: body
entity_type: node
bundle: post
label: Body
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
display_summary: true
required_summary: false
allowed_formats: { }
field_type: text_with_summary

View File

@ -1,37 +0,0 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_image
- node.type.post
module:
- image
id: node.post.field_image
field_name: field_image
entity_type: node
bundle: post
label: Image
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
handler: 'default:file'
handler_settings: { }
file_directory: '[date:custom:Y]-[date:custom:m]'
file_extensions: 'png gif jpg jpeg webp'
max_filesize: ''
max_resolution: ''
min_resolution: ''
alt_field: true
alt_field_required: true
title_field: false
title_field_required: false
default_image:
uuid: ''
alt: ''
title: ''
width: null
height: null
field_type: image

View File

@ -1,33 +0,0 @@
langcode: en
status: true
dependencies:
module:
- file
- image
- node
_core:
default_config_hash: EymokncRIZ7SgQT2IdOQhQJicX4nNc0K89ik-LxmOHE
id: node.field_image
field_name: field_image
entity_type: node
type: image
settings:
target_type: file
display_field: false
display_default: false
uri_scheme: public
default_image:
uuid: ''
alt: ''
title: ''
width: null
height: null
module: image
locked: false
cardinality: 1
translatable: true
indexes:
target_id:
- target_id
persist_with_no_fields: false
custom_storage: false

View File

@ -1,17 +0,0 @@
langcode: en
status: true
dependencies:
module:
- menu_ui
third_party_settings:
menu_ui:
available_menus:
- main
parent: 'main:'
name: Post
type: post
description: ''
help: ''
new_revision: true
preview_mode: 1
display_submitted: true

View File

@ -1,191 +0,0 @@
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.node.teaser
- node.type.post
module:
- node
- user
id: migration_based_posts
label: 'Migration Based Posts'
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
display:
default:
id: default
display_title: Default
display_plugin: default
position: 0
display_options:
title: 'Migration Based Posts'
fields:
title:
id: title
table: node_field_data
field: title
relationship: none
group_type: group
admin_label: ''
entity_type: node
entity_field: title
plugin_id: field
label: ''
exclude: false
alter:
alter_text: false
make_link: false
absolute: false
word_boundary: false
ellipsis: false
strip_tags: false
trim: false
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings:
link_to_entity: true
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
pager:
type: mini
options:
offset: 0
items_per_page: 10
total_pages: null
id: 0
tags:
next:
previous:
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
empty: { }
sorts:
created:
id: created
table: node_field_data
field: created
relationship: none
group_type: group
admin_label: ''
entity_type: node
entity_field: created
plugin_id: date
order: DESC
expose:
label: ''
field_identifier: ''
exposed: false
granularity: second
arguments: { }
filters:
status:
id: status
table: node_field_data
field: status
entity_type: node
entity_field: status
plugin_id: boolean
value: '1'
group: 1
expose:
operator: ''
type:
id: type
table: node_field_data
field: type
entity_type: node
entity_field: type
plugin_id: bundle
value:
post: post
style:
type: default
row:
type: 'entity:node'
options:
view_mode: teaser
query:
type: views_query
options:
query_comment: ''
disable_sql_rewrite: false
distinct: false
replica: false
query_tags: { }
relationships: { }
header: { }
footer: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
page_1:
id: page_1
display_title: Page
display_plugin: page
position: 1
display_options:
display_extenders: { }
path: migration-posts
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }

View File

@ -1,199 +0,0 @@
langcode: en
status: true
dependencies:
module:
- views_json_source
id: views_json_source_demo
label: 'Views JSON Source'
module: views
description: ''
tag: ''
base_table: json
base_field: ''
display:
default:
id: default
display_title: Default
display_plugin: default
position: 0
display_options:
title: 'Views JSON Source'
fields:
value:
id: value
table: json
field: value
relationship: none
group_type: group
admin_label: ''
entity_type: null
entity_field: null
plugin_id: views_json_source_field
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: h2
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
key: title
trusted_html: 0
value_1:
id: value_1
table: json
field: value
relationship: none
group_type: group
admin_label: ''
plugin_id: views_json_source_field
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
key: body
trusted_html: 0
pager:
type: mini
options:
offset: 0
items_per_page: 10
total_pages: null
id: 0
tags:
next:
previous:
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
access:
type: none
options: { }
cache:
type: tag
options: { }
empty: { }
sorts: { }
arguments: { }
filters: { }
style:
type: default
row:
type: fields
query:
type: views_query
options:
json_file: 'https://jsonplaceholder.typicode.com/posts'
row_apath: /
headers: ''
single_payload: 0
show_errors: 1
relationships: { }
header: { }
footer: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_interface'
- url.query_args
tags: { }
page_1:
id: page_1
display_title: Page
display_plugin: page
position: 1
display_options:
display_extenders: { }
path: views-json-source
cache_metadata:
max-age: -1
contexts:
- 'languages:language_interface'
- url.query_args
tags: { }

View File

@ -1,5 +0,0 @@
services:
json_ingestion_examples.commands:
class: Drupal\json_ingestion_examples\Drush\Commands\JsonIngestionCommands
tags:
- { name: drush.command }

View File

@ -1,9 +0,0 @@
name: JSON Ingestion Examples
type: module
package: custom
description: 'Ways to import JSON data to Drupal'
core_version_requirement: ^9 || ^10
dependencies:
- drupal:migrate
- drupal:migrate_plus
- drupal:views_json_source

View File

@ -1,6 +0,0 @@
<?php
function json_ingestion_examples_install() {
$mod_installer = \Drupal::service('module_installer');
$mod_installer->install(['migrate', 'migrate_plus', 'views_json_source']);
}

View File

@ -1,54 +0,0 @@
id: images
label: 'Import remote images.'
source:
plugin: url
data_fetcher_plugin: http
data_parser_plugin: json
urls: 'https://jsonplaceholder.typicode.com/photos'
item_selector: /
constants:
file_dest_dir: 'public://migrated-images/'
jpeg: '.jpeg'
ids:
image_id:
type: string
fields:
-
name: image_id
label: 'Image ID'
selector: /id
-
name: image_title
label: 'Image URL'
selector: /title
-
name: image_url
label: 'Image URL'
selector: /url
process:
_prep_filename:
-
plugin: callback
callable: basename
source: image_url
filename:
plugin: concat
source:
- '@_prep_filename'
- constants/jpeg
_prep_file_destination:
plugin: concat
source:
- constants/file_dest_dir
- '@filename'
uri:
plugin: file_copy
source:
- image_url
- '@_prep_file_destination'
status:
plugin: default_value
default_value: 1
alt: image_title
destination:
plugin: 'entity:file'

View File

@ -1,37 +0,0 @@
id: json_posts_to_posts
label: 'JSON Migrate - Posts to Posts'
migration_group: migrate_posts
source:
plugin: url
data_fetcher_plugin: http
data_parser_plugin: json
urls: 'https://jsonplaceholder.typicode.com/posts'
item_selector: /
ids:
post_id:
type: string
fields:
-
name: post_id
label: 'Post ID'
selector: /id
-
name: post_title
label: 'Post Title'
selector: /title
-
name: post_body
label: 'Post Body'
selector: /body
process:
title: post_title
body/value: post_body
field_image:
plugin: migration_lookup
migration: images
source: post_id # this gets an image with the same ID as the post.
destination:
plugin: 'entity:node'
default_bundle: post
migration_dependencies: { }

View File

@ -1,90 +0,0 @@
<?php
namespace Drupal\json_ingestion_examples\Drush\Commands;
use Drupal\Core\File\FileSystemInterface;
use Drupal\file\Entity\File;
use Drupal\node\Entity\Node;
use Drush\Commands\DrushCommands;
/**
* Drush command file.
*/
class JsonIngestionCommands extends DrushCommands {
/**
* A custom Drush command to displays the given text.
*
* @command json-ingestion-examples:update-posts
* The amount to iterate over
* @aliases jie-posts
*/
public function updatePosts() {
$this->importImages();
$this->importPosts();
}
/**
* Imports all images.
*/
public function importImages() {
// Go fetch JSON for images.
$http_client = \Drupal::httpClient();
$images_json_url = 'https://jsonplaceholder.typicode.com/photos';
$images_json = $http_client->request('GET', $images_json_url)->getBody()->getContents();
// Reduce to 100 images as only have 100 posts.
$images_array = array_slice(json_decode($images_json), 0, 100);
$directory = 'public://drush-generated';
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
// Make sure directory exists.
$file_system->prepareDirectory($directory, FileSystemInterface:: CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
foreach ($images_array as $img_ob) {
// Download file, always replace.
$file = file_get_contents($img_ob->url);
$filename = basename($img_ob->url) . '.jpeg';
$filepath = $directory . '/' . $filename;
$file_system->saveData($file, $filepath, FileSystemInterface::EXISTS_REPLACE);
// Create file entity.
$file = File::create([
'filename' => $filename,
'uri' => $filepath,
'status' => 1,
'uid' => 1,
]);
if ($file->save()) {
$this->writeln("File $filename created");
}
}
}
/**
* Imports all posts.
*/
public function importPosts() {
// Go fetch JSON for posts.
$http_client = \Drupal::httpClient();
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$posts_json_url = 'https://jsonplaceholder.typicode.com/posts';
$posts_json = $http_client->request('GET', $posts_json_url)->getBody()->getContents();
$posts_array = json_decode($posts_json);
foreach ($posts_array as $post) {
$node_check = $node_storage->loadByProperties([
'type' => 'post',
'title' => $post->title
]);
// If title already exists, skip.
if ($node_check) {
continue;
}
$new_post = Node::create(['type' => 'post']);
$new_post->set('title', $post->title);
$new_post->set('body', $post->body);
$new_post->set('field_image', $post->id);
$new_post->enforceIsNew();
if ($new_post->save()) {
$this->writeln("New post created: $post->title");
}
}
}
}

View File

@ -0,0 +1,5 @@
name: Video Compressor
description: 'Compresses videos to smaller size prior to upload.'
core_version_requirement: ^10 || ^11
package: Media
type: module

View File

@ -0,0 +1,10 @@
<?php
use Drupal\Core\Entity\EntityInterface;
/**
* Implements hook_entity_presave().
*/
function video_compressor_entity_presave(EntityInterface $entity) {
/* dump($entity); */
}

File diff suppressed because it is too large Load Diff

View File

@ -11,9 +11,12 @@
"author": "",
"license": "ISC",
"devDependencies": {
"autoprefixer": "^10.4.20",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"gulp-sass": "^5.1.0"
"gulp-sass": "^5.1.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.13"
},
"dependencies": {
"sass": "^1.71.1"

View File

@ -1,3 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
// Colors.
:root {
--site-primary: #009FB7;

View File

@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/**.{scss}"],
theme: {
extend: {},
},
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}