- { itemData.emojis.join(' ') }
+ { itemData.emojis.map(emoji => {
+ return
{ emoji }
+ }) }
{ itemData.colors.map((color, index) => (
diff --git a/src/js/component/tag-selector/tag-selector-items.jsx b/src/js/component/tag-selector/tag-selector-items.jsx
index 814c7e055..ab33390ad 100644
--- a/src/js/component/tag-selector/tag-selector-items.jsx
+++ b/src/js/component/tag-selector/tag-selector-items.jsx
@@ -7,7 +7,7 @@ import { useFocusManager, usePrevious } from 'web-common/hooks';
import { isTriggerEvent } from 'web-common/utils';
import { connectionIssues, checkColoredTags, fetchTags, navigate } from '../../actions';
-import { get, isOnlyEmoji } from '../../utils';
+import { containsEmoji, get } from '../../utils';
import { ITEM } from '../../constants/dnd';
import { useSourceSignature, useTags } from '../../hooks';
@@ -31,7 +31,7 @@ const Tag = memo(props => {
disabled: tag.disabled,
selected: tag.selected,
colored: tag.color,
- emoji: isOnlyEmoji(tag.tag),
+ emoji: containsEmoji(tag.tag),
placeholder: tag.isPlaceholder,
'dnd-target': isOver && canDrop
}) }
diff --git a/src/js/reducers/libraries/index.js b/src/js/reducers/libraries/index.js
index c2f622b52..590db26d4 100644
--- a/src/js/reducers/libraries/index.js
+++ b/src/js/reducers/libraries/index.js
@@ -35,7 +35,7 @@ const libraries = (state = {}, action, { itemsPublications, meta } = {}) => {
creating: creating(state[action.libraryKey]?.creating, action),
deleting: deleting(get(state, [action.libraryKey, 'deleting']), action),
items: items(get(state, [action.libraryKey, 'items']), action, {
- tagColors: state[action.libraryKey]?.tagColors?.lookup, meta
+ tagColors: (state[action.libraryKey]?.tagColors || {}), meta
} ),
itemsByCollection: itemsByCollection(get(state, [action.libraryKey, 'itemsByCollection']), action, {
items: (state[action.libraryKey] || {}).items, meta
diff --git a/src/js/reducers/libraries/items.js b/src/js/reducers/libraries/items.js
index 4a7054f7f..ebdbbc142 100644
--- a/src/js/reducers/libraries/items.js
+++ b/src/js/reducers/libraries/items.js
@@ -31,12 +31,12 @@ const discardIfOldVersion = (newItems, oldItems) => {
return result;
}
-const items = (state = {}, action, metaAndTags) => {
+const items = (state = {}, action, { meta, tagColors }) => {
switch(action.type) {
case RECEIVE_CREATE_ITEM:
return {
...state,
- [action.item.key]: calculateDerivedData(action.item, metaAndTags)
+ [action.item.key]: calculateDerivedData(action.item, { meta, tagColors })
};
case RECEIVE_DELETE_ITEM:
return omit(state, action.item.key);
@@ -48,7 +48,7 @@ const items = (state = {}, action, metaAndTags) => {
[action.itemKey]: calculateDerivedData({
...get(state, action.itemKey, {}),
...action.item
- }, metaAndTags)
+ }, { meta, tagColors })
};
case RECEIVE_UPDATE_MULTIPLE_ITEMS:
case RECEIVE_MOVE_ITEMS_TRASH:
@@ -61,7 +61,7 @@ const items = (state = {}, action, metaAndTags) => {
...indexByKey(Object.values(action.items), 'key', item => (calculateDerivedData({
...state[item.key],
...item
- }, metaAndTags)))
+ }, { meta, tagColors })))
}
case RECEIVE_CHILD_ITEMS:
case RECEIVE_CREATE_ITEMS:
@@ -75,7 +75,7 @@ const items = (state = {}, action, metaAndTags) => {
case RECEIVE_TRASH_ITEMS:
return {
...state,
- ...discardIfOldVersion(indexByKey(calculateDerivedData(action.items, metaAndTags), 'key'), state)
+ ...discardIfOldVersion(indexByKey(calculateDerivedData(action.items, { meta, tagColors }), 'key'), state)
};
case RECEIVE_UPLOAD_ATTACHMENT:
return {
@@ -104,12 +104,12 @@ const items = (state = {}, action, metaAndTags) => {
case RECEIVE_LIBRARY_SETTINGS:
case RECEIVE_UPDATE_LIBRARY_SETTINGS:
return action.settingsKey === 'tagColors' ? mapObject(state, (itemKey, item) => [itemKey, calculateDerivedData(item, {
- meta: metaAndTags.meta,
- tagColors: indexByKey(action.value ?? [], 'name', ({ color }) => color)
+ meta,
+ tagColors: { value: action.value ?? [], lookup: indexByKey(action.value ?? [], 'name', ({ color }) => color)}
})]) : state;
case RECEIVE_DELETE_LIBRARY_SETTINGS:
return action.settingsKey === 'tagColors' ? mapObject(state, (itemKey, item) => [itemKey, calculateDerivedData(item, {
- meta: metaAndTags.meta,
+ meta,
tagColors: {}
})]) : state;
case RECEIVE_DELETE_TAGS:
diff --git a/src/js/utils.js b/src/js/utils.js
index 78a06c718..8634d2e80 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -474,13 +474,20 @@ const getPrevSibling = (elem, selector) => {
}
};
-// https://github.com/zotero/zotero/blob/256bd157edd7707aa1affa1822f68f41be1f988c/chrome/content/zotero/xpcom/utilities_internal.js#L408
-const isOnlyEmoji = str => {
- // Remove emoji, Zero Width Joiner, and Variation Selector-16 and see if anything's left
- const re = /\p{Extended_Pictographic}|\u200D|\uFE0F/gu;
- return !str.replace(re, '');
+// https://github.com/zotero/zotero/blob/214a668286d35a630db293bb835f544693698297/chrome/content/zotero/xpcom/utilities_internal.js#L399-L408
+const containsEmoji = str => {
+ let re = /\p{Extended_Pictographic}/gu;
+ return !!str.match(re);
}
+// https://github.com/abaevbog/zotero/blob/f8fd90945663d4745306d88be298633f8c7229de/chrome/content/zotero/xpcom/data/tags.js#L1000
+const extractEmoji = str => {
+ // Split by anything that is not an emoji, Zero Width Joiner, or Variation Selector-16
+ // And return first continuous span of emojis
+ let re = /[^\p{Extended_Pictographic}\u200D\uFE0F]+/gu; //eslint-disable-line no-misleading-character-class
+ return str.split(re).filter(Boolean)[0] || null;
+};
+
const isReaderCompatibleBrowser = () => typeof structuredClone === "function";
export {
@@ -491,10 +498,12 @@ export {
cleanURL,
compare,
compareItem,
+ containsEmoji,
deduplicate,
deduplicateByHash,
deduplicateByKey,
enumerateObjects,
+ extractEmoji,
get,
getAbortController,
getDOIURL,
@@ -512,7 +521,6 @@ export {
indexByGeneratedKey,
indexByKey,
isLikeURL,
- isOnlyEmoji,
isReaderCompatibleBrowser,
JSONTryParse,
localStorageWrapper,
diff --git a/src/scss/components/_icon-extra.scss b/src/scss/components/_icon-extra.scss
index e3097b78d..0f1932f13 100644
--- a/src/scss/components/_icon-extra.scss
+++ b/src/scss/components/_icon-extra.scss
@@ -22,11 +22,11 @@
}
}
-.icon-crescent-circle,
-.icon-crescent-circle-active {
- margin-left: -2px;
+.icon-crescent-circle {
+ margin-left: -5px;
}
+
.icon-circle, .icon-crescent-circle {
color: $icon-circle-color;
diff --git a/src/scss/components/item/_list.scss b/src/scss/components/item/_list.scss
index a54936dcc..7a3e1fc29 100644
--- a/src/scss/components/item/_list.scss
+++ b/src/scss/components/item/_list.scss
@@ -223,6 +223,10 @@
display: none;
}
+ .emoji {
+ margin-left: 4px;
+ }
+
.tag-circles {
display: inline-flex;
margin-left: $space-min;
diff --git a/src/scss/components/item/_table.scss b/src/scss/components/item/_table.scss
index be0958ffc..6f72498ce 100644
--- a/src/scss/components/item/_table.scss
+++ b/src/scss/components/item/_table.scss
@@ -332,8 +332,14 @@
overflow: hidden;
padding-left: $space-xs;
+ .emoji {
+ margin-left: 4px;
+ }
+
.tag-circles {
- display: inline-flex;
+ // match the behaviour of the client where tag swatches are displayed flipped
+ // https://github.com/zotero/zotero/blob/214a668286d35a630db293bb835f544693698297/scss/components/_item-tree.scss#L106
+ transform: scaleX(-1);
margin-left: $space-min;
}
}
diff --git a/src/static/icons/10/circle.svg b/src/static/icons/10/circle.svg
deleted file mode 100644
index ffc252a90..000000000
--- a/src/static/icons/10/circle.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
diff --git a/src/static/icons/10/crescent-circle.svg b/src/static/icons/10/crescent-circle.svg
deleted file mode 100644
index 6bb8b168a..000000000
--- a/src/static/icons/10/crescent-circle.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
diff --git a/src/static/icons/12/circle.svg b/src/static/icons/12/circle.svg
index 51711b52a..63c2d8d78 100644
--- a/src/static/icons/12/circle.svg
+++ b/src/static/icons/12/circle.svg
@@ -1,8 +1,6 @@