diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3c48e7d34..3049a872a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -48,7 +48,7 @@ jobs:
- image: cimg/node:16.11.0
working_directory: ~/homebrewery
- parallelism: 4
+ parallelism: 1
steps:
- attach_workspace:
@@ -61,15 +61,15 @@ jobs:
- run:
name: Test - Basic
command: npm run test:basic
- - run:
- name: Test - Coverage
- command: npm run test:coverage
- run:
name: Test - Mustache Spans
command: npm run test:mustache-span
- run:
name: Test - Routes
command: npm run test:route
+ - run:
+ name: Test - Coverage
+ command: npm run test:coverage
workflows:
build_and_test:
diff --git a/changelog.md b/changelog.md
index 0260a1f44..60719b916 100644
--- a/changelog.md
+++ b/changelog.md
@@ -80,6 +80,50 @@ pre {
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
+### XXXXday DD/MM/2023 - v3.8.0
+{{taskList
+##### G-Ambatte
+
+* [x] Update server build scripts to fix Admin page
+
+Fixes issues [#2657](https://github.com/naturalcrit/homebrewery/issues/2657)
+
+* [x] Fix internal links inside `
` blocks not automatically receiving the `target=_self` attribute
+
+Fixes issues [#2680](https://github.com/naturalcrit/homebrewery/issues/2680)
+}}
+
+### Monday 13/03/2023 - v3.7.2
+{{taskList
+
+##### Calculuschild
+
+* [x] Fix wide Monster Stat Blocks not spanning columns on Legacy
+}}
+
+### Thursday 09/03/2023 - v3.7.1
+{{taskList
+
+##### Lucastucious (new contributor!)
+
+* [x] Changed `filter: drop-shadow` to `box-shadow` on text boxes, making text selectable in PDFs again.
+
+Fixes issues [#1569](https://github.com/naturalcrit/homebrewery/issues/1569)
+
+{{note
+**NOTE:** If you create your PDF on a computer with an old version of Mac Preview (v10 or older) you may see shadows appear as solid gray.
+}}
+
+##### MichielDeMey
+
+* [x] Updated the Google Drive icon
+* [x] Backend fix to unit tests failing intermittently
+
+##### Calculuschild
+
+* [x] Fix PDF pixelation on CoverPage text outlines
+}}
+
### Tuesday 28/02/2023 - v3.7.0
{{taskList
@@ -115,7 +159,6 @@ Fixes issues [#2687](https://github.com/naturalcrit/homebrewery/issues/2687)
Fixes issues [#2674](https://github.com/naturalcrit/homebrewery/issues/2674)
}}
-
### Monday 23/01/2023 - v3.6.0
{{taskList
##### calculuschild
@@ -141,8 +184,6 @@ Fixes issues [#2583](https://github.com/naturalcrit/homebrewery/issues/2583)
* [x] Fix cloned brews inheriting the parent view count
}}
-\column
-
### Friday 23/12/2022 - v3.5.0
{{taskList
diff --git a/client/components/combobox.jsx b/client/components/combobox.jsx
new file mode 100644
index 000000000..a6e699dcf
--- /dev/null
+++ b/client/components/combobox.jsx
@@ -0,0 +1,129 @@
+const React = require('react');
+const createClass = require('create-react-class');
+const _ = require('lodash');
+const cx = require('classnames');
+require('./combobox.less');
+
+const Combobox = createClass({
+ displayName : 'Combobox',
+ getDefaultProps : function() {
+ return {
+ className : '',
+ trigger : 'hover',
+ default : '',
+ placeholder : '',
+ autoSuggest : {
+ clearAutoSuggestOnClick : true,
+ suggestMethod : 'includes',
+ filterOn : [] // should allow as array to filter on multiple attributes, or even custom filter
+ },
+ };
+ },
+ getInitialState : function() {
+ return {
+ showDropdown : false,
+ value : '',
+ options : [...this.props.options],
+ inputFocused : false
+ };
+ },
+ componentDidMount : function() {
+ if(this.props.trigger == 'click')
+ document.addEventListener('click', this.handleClickOutside);
+ this.setState({
+ value : this.props.default
+ });
+ },
+ componentWillUnmount : function() {
+ if(this.props.trigger == 'click')
+ document.removeEventListener('click', this.handleClickOutside);
+ },
+ handleClickOutside : function(e){
+ // Close dropdown when clicked outside
+ if(this.refs.dropdown && !this.refs.dropdown.contains(e.target)) {
+ this.handleDropdown(false);
+ }
+ },
+ handleDropdown : function(show){
+ this.setState({
+ showDropdown : show,
+ inputFocused : this.props.autoSuggest.clearAutoSuggestOnClick ? show : false
+ });
+ },
+ handleInput : function(e){
+ e.persist();
+ this.setState({
+ value : e.target.value,
+ inputFocused : false
+ }, ()=>{
+ this.props.onEntry(e);
+ });
+ },
+ handleSelect : function(e){
+ this.setState({
+ value : e.currentTarget.getAttribute('data-value')
+ }, ()=>{this.props.onSelect(this.state.value);});
+ ;
+ },
+ renderTextInput : function(){
+ return (
+
{this.handleDropdown(true);} : undefined}
+ onClick= {this.props.trigger == 'click' ? ()=>{this.handleDropdown(true);} : undefined}>
+ this.handleInput(e)}
+ value={this.state.value || ''}
+ placeholder={this.props.placeholder}
+ onBlur={(e)=>{
+ if(!e.target.checkValidity()){
+ this.setState({
+ value : this.props.default
+ }, ()=>this.props.onEntry(e));
+ }
+ }}
+ />
+
+ );
+ },
+ renderDropdown : function(dropdownChildren){
+ if(!this.state.showDropdown) return null;
+ if(this.props.autoSuggest && !this.state.inputFocused){
+ const suggestMethod = this.props.autoSuggest.suggestMethod;
+ const filterOn = _.isString(this.props.autoSuggest.filterOn) ? [this.props.autoSuggest.filterOn] : this.props.autoSuggest.filterOn;
+ const filteredArrays = filterOn.map((attr)=>{
+ const children = dropdownChildren.filter((item)=>{
+ if(suggestMethod === 'includes'){
+ return item.props[attr]?.toLowerCase().includes(this.state.value.toLowerCase());
+ } else if(suggestMethod === 'startsWith'){
+ return item.props[attr]?.toLowerCase().startsWith(this.state.value.toLowerCase());
+ }
+ });
+ return children;
+ });
+ dropdownChildren = _.uniq(filteredArrays.flat(1));
+ }
+
+ return (
+
+ {dropdownChildren}
+
+ );
+ },
+ render : function () {
+ const dropdownChildren = this.state.options.map((child, i)=>{
+ const clone = React.cloneElement(child, { onClick: (e)=>this.handleSelect(e) });
+ return clone;
+ });
+ return (
+
{this.handleDropdown(false);} : undefined}>
+ {this.renderTextInput()}
+ {this.renderDropdown(dropdownChildren)}
+
+ );
+ }
+});
+
+module.exports = Combobox;
diff --git a/client/components/combobox.less b/client/components/combobox.less
new file mode 100644
index 000000000..3810a874e
--- /dev/null
+++ b/client/components/combobox.less
@@ -0,0 +1,50 @@
+.dropdown-container {
+ position:relative;
+ input {
+ width: 100%;
+ }
+ .dropdown-options {
+ position:absolute;
+ background-color: white;
+ z-index: 100;
+ width: 100%;
+ border: 1px solid gray;
+ overflow-y: auto;
+ max-height: 200px;
+
+ &::-webkit-scrollbar {
+ width: 14px;
+ }
+ &::-webkit-scrollbar-track {
+ background: #ffffff;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: #949494;
+ border-radius: 10px;
+ border: 3px solid #ffffff;
+ }
+
+ .item {
+ position:relative;
+ font-size: 11px;
+ font-family: Open Sans;
+ padding: 5px;
+ cursor: default;
+ margin: 0 3px;
+ //border-bottom: 1px solid darkgray;
+ &:hover {
+ filter: brightness(120%);
+ background-color: rgb(163, 163, 163);
+ }
+ .detail {
+ width:100%;
+ text-align: left;
+ color: rgb(124, 124, 124);
+ font-style:italic;
+ font-size: 9px;
+ }
+ }
+
+ }
+
+}
diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx
index a41e01228..27fef7e16 100644
--- a/client/homebrew/brewRenderer/brewRenderer.jsx
+++ b/client/homebrew/brewRenderer/brewRenderer.jsx
@@ -27,6 +27,7 @@ const BrewRenderer = createClass({
style : '',
renderer : 'legacy',
theme : '5ePHB',
+ lang : '',
errors : []
};
},
@@ -190,7 +191,6 @@ const BrewRenderer = createClass({
const rendererPath = this.props.renderer == 'V3' ? 'V3' : 'Legacy';
const themePath = this.props.theme ?? '5ePHB';
const baseThemePath = Themes[rendererPath][themePath].baseTheme;
-
return (
{!this.state.isMounted
@@ -223,7 +223,7 @@ const BrewRenderer = createClass({
&&
<>
{this.renderStyle()}
-
+
{this.renderPages()}
>
diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx
index 3d77f557f..074879b05 100644
--- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx
+++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx
@@ -6,6 +6,7 @@ const _ = require('lodash');
const cx = require('classnames');
const request = require('../../utils/request-middleware.js');
const Nav = require('naturalcrit/nav/nav.jsx');
+const Combobox = require('client/components/combobox.jsx');
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
const Themes = require('themes/themes.json');
@@ -35,7 +36,8 @@ const MetadataEditor = createClass({
authors : [],
systems : [],
renderer : 'legacy',
- theme : '5ePHB'
+ theme : '5ePHB',
+ lang : 'en'
},
onChange : ()=>{},
reportError : ()=>{}
@@ -76,6 +78,7 @@ const MetadataEditor = createClass({
const errMessage = validationErr.map((err)=>{
return `- ${err}`;
}).join('\n');
+
callIfExists(e.target, 'setCustomValidity', errMessage);
callIfExists(e.target, 'reportValidity');
}
@@ -111,6 +114,11 @@ const MetadataEditor = createClass({
this.props.onChange(this.props.metadata);
},
+ handleLanguage : function(languageCode){
+ this.props.metadata.lang = languageCode;
+ this.props.onChange(this.props.metadata);
+ },
+
handleDelete : function(){
if(this.props.metadata.authors && this.props.metadata.authors.length <= 1){
if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return;
@@ -224,6 +232,47 @@ const MetadataEditor = createClass({
;
},
+ renderLanguageDropdown : function(){
+ const langCodes = ['en', 'de', 'de-ch', 'fr', 'ja', 'es', 'it', 'sv', 'ru', 'zh-Hans', 'zh-Hant'];
+ const listLanguages = ()=>{
+ return _.map(langCodes.sort(), (code, index)=>{
+ const localName = new Intl.DisplayNames([code], { type: 'language' });
+ const englishName = new Intl.DisplayNames('en', { type: 'language' });
+ return
+ {`${code}`}
+
{`${localName.of(code)}`}
+
;
+ });
+ };
+
+ const debouncedHandleFieldChange = _.debounce(this.handleFieldChange, 500);
+
+ return
+
language
+
+ this.handleLanguage(value)}
+ onEntry={(e)=>{
+ e.target.setCustomValidity(''); //Clear the validation popup while typing
+ debouncedHandleFieldChange('lang', e);
+ }}
+ options={listLanguages()}
+ autoSuggest={{
+ suggestMethod : 'startsWith',
+ clearAutoSuggestOnClick : true,
+ filterOn : ['data-value', 'data-detail', 'title']
+ }}
+ >
+
+ Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.
+
+
+
;
+ },
+
renderRenderOptions : function(){
if(!global.enable_v3) return;
@@ -301,6 +350,8 @@ const MetadataEditor = createClass({
+ {this.renderLanguageDropdown()}
+
{this.renderThemeDropdown()}
{this.renderRenderOptions()}
@@ -315,7 +366,7 @@ const MetadataEditor = createClass({
validators={[(v)=>!this.props.metadata.authors?.includes(v)]}
placeholder='invite author' unique={true}
values={this.props.metadata.invitedAuthors}
- notes={['Invited authors are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
+ notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}/>