Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/assets_code_analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jobs:
- name: Lint on SCSS files
run: npm run stylelint

- name: Check TypeScript types
run: npm run typecheck

- name: Build for production
run: npm run build

Expand Down
3 changes: 2 additions & 1 deletion assets/js/app/editor/Components/Html.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<script>
import trumbowyg from 'vue-trumbowyg';
import 'trumbowyg/dist/ui/trumbowyg.css';
import { strip } from '../../../filters/string';

export default {
name: 'EditorHtml',
Expand Down Expand Up @@ -40,7 +41,7 @@ export default {
};
},
mounted() {
this.val = this.$options.filters.strip(this.value);
this.val = strip(this.value);
},
};
</script>
3 changes: 2 additions & 1 deletion assets/js/app/editor/Components/Markdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<script>
import VueEasymde from 'vue-easymde';
import { strip } from '../../../filters/string';

export default {
name: 'EditorMarkdown',
Expand All @@ -27,7 +28,7 @@ export default {
};
},
mounted() {
this.val = this.$options.filters.strip(this.value);
this.val = strip(this.value);
},
};
</script>
Expand Down
6 changes: 4 additions & 2 deletions assets/js/app/editor/Components/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
>
<template v-if="name === 'status'" slot="singleLabel" slot-scope="props">
<span class="status me-2" :class="`is-${props.option.key}`"></span>
{{ props.option.value | raw }}
{{ raw(props.option.value) }}
</template>
<template v-if="name === 'status'" #option="props">
<span class="status me-2" :class="`is-${props.option.key}`"></span>
{{ props.option.value | raw }}
{{ raw(props.option.value) }}
</template>

<template v-if="props.option.link_to_record_url" slot="singleLabel" slot-scope="props">
Expand Down Expand Up @@ -80,6 +80,7 @@
<script>
import Multiselect from 'vue-multiselect';
import $ from 'jquery';
import { raw } from '../../../filters/string';

export default {
name: 'EditorSelect',
Expand Down Expand Up @@ -181,6 +182,7 @@ export default {
}
},
methods: {
raw,
addTag(newTag) {
const tag = {
key: newTag,
Expand Down
5 changes: 3 additions & 2 deletions assets/js/app/editor/Components/Slug.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@

<script>
import field from '../mixins/value';
import { slugify } from '../../../filters/string';

export default {
name: 'EditorSlug',
Expand Down Expand Up @@ -106,7 +107,7 @@ export default {
},
lockSlug() {
this.$root.$emit('generate-from-title', false);
const slug = this.$options.filters.slugify(this.val);
const slug = slugify(this.val);
this.val = slug;
this.edit = false;
this.locked = true;
Expand All @@ -119,7 +120,7 @@ export default {
title = title + ' ' + document.querySelector(`input[name='fields[${element}]']`).value;
});

const slug = this.$options.filters.slugify(title);
const slug = slugify(title);
this.val = slug;
this.$root.$emit('generate-from-title', true);

Expand Down
15 changes: 11 additions & 4 deletions assets/js/app/listing/Components/Table/Row/_Actions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,29 +73,32 @@
<span class="dropdown-item-text">
<i class="fas fa-link fa-w"></i>
{{ labels.slug }}:
<code :title="slug">{{ slug | trim(24) }}</code>
<code :title="slug">{{ trim(slug, 24) }}</code>
</span>
<span class="dropdown-item-text">
<i class="fas fa-asterisk fa-w"></i>
{{ labels.created_on }}:
<strong>{{ record.createdAt | datetime }}</strong>
<strong>{{ datetime(record.createdAt) }}</strong>
</span>
<span class="dropdown-item-text">
<i class="far fa-calendar-alt fa-w"></i>
{{ labels.published_on }}:
<strong>{{ record.publishedAt | datetime }}</strong>
<strong>{{ datetime(record.publishedAt) }}</strong>
</span>
<span class="dropdown-item-text">
<i class="fas fa-redo fa-w"></i>
{{ labels.last_modified_on }}:
<strong>{{ record.modifiedAt | datetime }}</strong>
<strong>{{ datetime(record.modifiedAt) }}</strong>
</span>
</div>
</div>
</div>
</template>

<script>
import { datetime } from '../../../../../filters/date';
import { trim } from '../../../../../filters/string';

export default {
name: 'Actions',
props: {
Expand All @@ -118,5 +121,9 @@ export default {
created() {
// console.log(this.labels);
},
methods: {
datetime,
trim,
},
};
</script>
7 changes: 6 additions & 1 deletion assets/js/app/listing/Components/Table/Row/_Meta.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ul class="listing__row--list">
<li class="text-nowrap">
<span class="status" :class="`is-${record.status}`" :title="record.status"></span>
{{ record.publishedAt ? record.publishedAt : record.createdAt | date }}
{{ date(record.publishedAt ? record.publishedAt : record.createdAt) }}
</li>
<li v-if="size === 'normal'"><i class="fas fa-user"></i> {{ record.authorName }}</li>
<li v-if="size === 'normal'">
Expand All @@ -20,12 +20,17 @@
</template>

<script>
import { date } from '../../../../../filters/date';

export default {
name: 'MetaData',
props: {
type: String,
size: String,
record: Object,
},
methods: {
date,
},
};
</script>
9 changes: 7 additions & 2 deletions assets/js/app/listing/Components/Table/Row/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
<!-- column details / excerpt -->
<div class="listing__row--item is-details">
<a class="listing__row--item-title text-decoration-none" :href="record.extras.editLink" :title="slug">
{{ record.extras.title | trim(62) | raw }}
{{ raw(trim(record.extras.title, 62)) }}
</a>
<span v-if="record.extras.feature" class="badge" :class="`badge-${record.extras.feature}`">{{
record.extras.feature
}}</span>
<span class="listing__row--item-title-excerpt">{{ record.extras.excerpt | raw }}</span>
<span class="listing__row--item-title-excerpt">{{ raw(record.extras.excerpt) }}</span>
</div>
<!-- end column -->

Expand Down Expand Up @@ -46,6 +46,7 @@

<script>
import type from '../../../mixins/type';
import { raw, trim } from '../../../../../filters/string';
import Checkbox from './_Checkbox';
import Meta from './_Meta';
import Actions from './_Actions';
Expand Down Expand Up @@ -80,5 +81,9 @@ export default {
return this.$store.getters['general/getSorting'];
},
},
methods: {
raw,
trim,
},
};
</script>
1 change: 0 additions & 1 deletion assets/js/bolt.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ Axios.defaults.headers.common = {

import './jquery';
import './codemirror';
import './filters';
import './app';
import '../scss/bolt.scss';
13 changes: 8 additions & 5 deletions assets/js/filters/date.js → assets/js/filters/date.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import Vue from 'vue';
import { DateTime } from 'luxon';

Vue.filter('date', string => {
export function date(string: unknown) {
if (string) {
return DateTime.fromISO(String(string)).toLocaleString();
}
});

Vue.filter('datetime', string => {
return undefined;
}

export function datetime(string: unknown) {
if (string) {
return DateTime.fromISO(String(string)).toLocaleString(DateTime.DATETIME_MED);
}
});

return undefined;
}
2 changes: 0 additions & 2 deletions assets/js/filters/index.js

This file was deleted.

2 changes: 2 additions & 0 deletions assets/js/filters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { date, datetime } from './date';
export { raw, slugify, strip, trim, uppercase } from './string';
81 changes: 50 additions & 31 deletions assets/js/filters/string.js → assets/js/filters/string.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import Vue from 'vue';

Vue.filter('slugify', string => {
export function slugify(string: unknown) {
if (string) {
// based on https://gist.github.com/sgmurphy/3095196
string = String(string);
let opt = {
let slug = String(string);
const opt = {
delimiter: '-',
lowercase: true,
transliterate: typeof XRegExp === 'undefined' ? true : false,
// XRegExp is never bundled, so this is always true. Kept as-is from
// the original gist rather than silently changing the behaviour;
// reading it off globalThis is what the bare identifier did.
transliterate: typeof (globalThis as Record<string, unknown>).XRegExp === 'undefined' ? true : false,
};
let char_map = {
const char_map: Record<string, string> = {
// Latin
À: 'A',
Á: 'A',
Expand Down Expand Up @@ -304,48 +305,66 @@ Vue.filter('slugify', string => {
};

if (opt.transliterate) {
for (var k in char_map) {
string = string.replace(RegExp(k, 'g'), char_map[k]);
for (const k in char_map) {
slug = slug.replace(RegExp(k, 'g'), char_map[k]);
}
}

// Replace non-alphanumeric characters with our delimiter
var alnum = RegExp('[^a-z0-9]+', 'ig');
string = string.replace(alnum, opt.delimiter);
const alnum = RegExp('[^a-z0-9]+', 'ig');
slug = slug.replace(alnum, opt.delimiter);

// Remove duplicate delimiters
string = string.replace(RegExp('[' + opt.delimiter + ']{2,}', 'g'), opt.delimiter);
slug = slug.replace(RegExp('[' + opt.delimiter + ']{2,}', 'g'), opt.delimiter);

// Remove delimiter from ends
string = string.replace(RegExp('(^' + opt.delimiter + '|' + opt.delimiter + '$)', 'g'), '');
slug = slug.replace(RegExp('(^' + opt.delimiter + '|' + opt.delimiter + '$)', 'g'), '');

string = opt.lowercase ? string.toLowerCase() : string;
return string.replace(/[^\w-]+/g, '');
slug = opt.lowercase ? slug.toLowerCase() : slug;
return slug.replace(/[^\w-]+/g, '');
}
});

Vue.filter('strip', string => {
return undefined;
}

export function strip(string: unknown) {
if (string) {
return string.replace(/(^")|("$)/g, '');
return String(string).replace(/(^")|("$)/g, '');
}
});

Vue.filter('raw', string => {
return undefined;
}

export function raw(string: unknown) {
if (string) {
let node = document.createElement('textarea');
node.innerHTML = string;
const node = document.createElement('textarea');
node.innerHTML = String(string);
return node.value;
}
});

Vue.filter('uppercase', string => {
if (string) return string.toUpperCase();
});
return undefined;
}

Vue.filter('trim', (string, length) => {
if (length == undefined) {
length = 50;
export function uppercase(string: unknown) {
if (string) return String(string).toUpperCase();

return undefined;
}

export function trim(string: unknown, length?: number | null) {
if (string === null || string === undefined) {
return undefined;
}

return string.length > length ? string.substring(0, length - 1) + '…' : string;
});
// `?? 50` rather than a default parameter: the original used `length ==
// undefined`, which also catches null, and a default parameter would not.
const max = length ?? 50;
// Deliberately not String()-coerced. The original read .length straight off
// the argument, so a non-string (a number, an object) had an undefined
// length, failed the comparison and was returned untouched for Vue's own
// stringifier to render. Coercing here would truncate numbers and turn
// objects into "[object Object]".
const str = string as string;

return str.length > max ? str.substring(0, max - 1) + '…' : str;
}
3 changes: 0 additions & 3 deletions assets/js/services/api/content.js

This file was deleted.

9 changes: 9 additions & 0 deletions assets/js/services/api/content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function getRecords(type: string) {
// String() keeps the previous behaviour for a missing key: getItem returns
// null, JSON.parse coerces it to "null" and yields null.
// The local is typed so the inferred return is unknown rather than the any
// that JSON.parse hands back.
const records: unknown = JSON.parse(String(localStorage.getItem(`records-${type}`)));

return records;
}
12 changes: 0 additions & 12 deletions assets/js/services/api/dashboardNews.js

This file was deleted.

Loading
Loading