@ndla/ui 22.0.1 → 22.0.2

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.
Files changed (53) hide show
  1. package/es/ContentLoader/index.js +8 -3
  2. package/es/Resource/BlockResource.js +105 -18
  3. package/es/Resource/ListResource.js +124 -26
  4. package/es/SnackBar/DefaultSnackbar.js +56 -0
  5. package/es/SnackBar/SnackbarProvider.js +179 -0
  6. package/es/SnackBar/index.js +2 -2
  7. package/es/index.js +1 -1
  8. package/es/locale/messages-en.js +9 -2
  9. package/es/locale/messages-nb.js +9 -2
  10. package/es/locale/messages-nn.js +9 -2
  11. package/es/locale/messages-se.js +9 -2
  12. package/es/locale/messages-sma.js +9 -2
  13. package/lib/ContentLoader/index.d.ts +4 -8
  14. package/lib/ContentLoader/index.js +8 -3
  15. package/lib/Resource/BlockResource.d.ts +2 -1
  16. package/lib/Resource/BlockResource.js +106 -18
  17. package/lib/Resource/ListResource.d.ts +2 -1
  18. package/lib/Resource/ListResource.js +125 -26
  19. package/lib/SnackBar/DefaultSnackbar.d.ts +11 -0
  20. package/lib/SnackBar/DefaultSnackbar.js +70 -0
  21. package/lib/SnackBar/SnackbarProvider.d.ts +32 -0
  22. package/lib/SnackBar/SnackbarProvider.js +197 -0
  23. package/lib/SnackBar/index.d.ts +3 -3
  24. package/lib/SnackBar/index.js +23 -3
  25. package/lib/index.d.ts +2 -2
  26. package/lib/index.js +24 -3
  27. package/lib/locale/messages-en.d.ts +7 -0
  28. package/lib/locale/messages-en.js +9 -2
  29. package/lib/locale/messages-nb.d.ts +7 -0
  30. package/lib/locale/messages-nb.js +9 -2
  31. package/lib/locale/messages-nn.d.ts +7 -0
  32. package/lib/locale/messages-nn.js +9 -2
  33. package/lib/locale/messages-se.d.ts +7 -0
  34. package/lib/locale/messages-se.js +9 -2
  35. package/lib/locale/messages-sma.d.ts +7 -0
  36. package/lib/locale/messages-sma.js +9 -2
  37. package/package.json +5 -5
  38. package/src/ContentLoader/index.tsx +9 -9
  39. package/src/Resource/BlockResource.tsx +65 -4
  40. package/src/Resource/ListResource.tsx +85 -10
  41. package/src/SnackBar/DefaultSnackbar.tsx +70 -0
  42. package/src/SnackBar/SnackbarProvider.tsx +147 -0
  43. package/src/SnackBar/index.ts +3 -5
  44. package/src/index.ts +2 -2
  45. package/src/locale/messages-en.ts +7 -0
  46. package/src/locale/messages-nb.ts +7 -0
  47. package/src/locale/messages-nn.ts +7 -0
  48. package/src/locale/messages-se.ts +7 -0
  49. package/src/locale/messages-sma.ts +7 -0
  50. package/es/SnackBar/SnackBar.js +0 -117
  51. package/lib/SnackBar/SnackBar.d.ts +0 -23
  52. package/lib/SnackBar/SnackBar.js +0 -127
  53. package/src/SnackBar/SnackBar.tsx +0 -183
@@ -13,6 +13,7 @@ import { colors, fonts, spacing } from '@ndla/core';
13
13
  import { MenuButton, MenuItemProps } from '@ndla/button';
14
14
  import Image from '../Image';
15
15
  import { CompressedTagList, ResourceImageProps, ResourceTitle, Row, TopicList } from './resourceComponents';
16
+ import ContentLoader from '../ContentLoader';
16
17
 
17
18
  interface BlockResourceProps {
18
19
  link: string;
@@ -22,6 +23,7 @@ interface BlockResourceProps {
22
23
  tags?: string[];
23
24
  description?: string;
24
25
  menuItems?: MenuItemProps[];
26
+ isLoading?: boolean;
25
27
  }
26
28
 
27
29
  const BlockElementWrapper = styled(SafeLink)`
@@ -78,17 +80,76 @@ const ImageWrapper = styled.div`
78
80
  }
79
81
  `;
80
82
 
81
- const BlockResource = ({ link, title, tags, resourceImage, topics, description, menuItems }: BlockResourceProps) => {
83
+ interface BlockImageProps {
84
+ image: ResourceImageProps;
85
+ loading?: boolean;
86
+ }
87
+
88
+ const BlockImage = ({ image, loading }: BlockImageProps) => {
89
+ if (loading) {
90
+ return (
91
+ <ContentLoader height={'100%'} width={'100%'} viewBox={null} preserveAspectRatio="none">
92
+ <rect x="0" y="0" rx="3" ry="3" width="100%" height="100%" />
93
+ </ContentLoader>
94
+ );
95
+ }
96
+ return <Image alt={image.alt} src={image.src} />;
97
+ };
98
+
99
+ interface BlockTitleProps {
100
+ title: string;
101
+ loading?: boolean;
102
+ }
103
+
104
+ const BlockTitle = ({ title, loading }: BlockTitleProps) => {
105
+ if (loading) {
106
+ return (
107
+ <ContentLoader height={'18px'} width={'100%'} viewBox={null} preserveAspectRatio="none">
108
+ <rect x="0" y="0" rx="3" ry="3" width="100%" height="18px" />
109
+ </ContentLoader>
110
+ );
111
+ }
112
+ return <ResourceTitle>{title}</ResourceTitle>;
113
+ };
114
+
115
+ interface BlockTopicListProps {
116
+ topics: string[];
117
+ loading?: boolean;
118
+ }
119
+
120
+ const BlockTopicList = ({ topics, loading }: BlockTopicListProps) => {
121
+ if (loading) {
122
+ return (
123
+ <ContentLoader height={'18px'} width={'100%'} viewBox={null} preserveAspectRatio="none">
124
+ <rect x="0" y="0" rx="3" ry="3" width="20%" height="18px" />
125
+ <rect x="25%" y="0" rx="3" ry="3" width="20%" height="18px" />
126
+ </ContentLoader>
127
+ );
128
+ }
129
+
130
+ return <TopicList topics={topics} />;
131
+ };
132
+
133
+ const BlockResource = ({
134
+ link,
135
+ title,
136
+ tags,
137
+ resourceImage,
138
+ topics,
139
+ description,
140
+ menuItems,
141
+ isLoading,
142
+ }: BlockResourceProps) => {
82
143
  return (
83
144
  <BlockElementWrapper to={link}>
84
145
  <ImageWrapper>
85
- <Image alt={resourceImage.alt} src={resourceImage.src} />
146
+ <BlockImage image={resourceImage} loading={isLoading} />
86
147
  </ImageWrapper>
87
148
  <BlockInfoWrapper>
88
149
  <div>
89
- <ResourceTitle>{title}</ResourceTitle>
150
+ <BlockTitle title={title} loading={isLoading} />
90
151
  </div>
91
- <TopicList topics={topics} />
152
+ <BlockTopicList topics={topics} loading={isLoading} />
92
153
  <BlockDescription>{description}</BlockDescription>
93
154
  <RightRow>
94
155
  {tags && <CompressedTagList tags={tags} />}
@@ -13,8 +13,9 @@ import { fonts, spacing, colors, breakpoints, mq } from '@ndla/core';
13
13
  import { MenuButton, MenuItemProps } from '@ndla/button';
14
14
  import Image from '../Image';
15
15
  import { CompressedTagList, ResourceImageProps, ResourceTitle, TopicList } from './resourceComponents';
16
+ import ContentLoader from '../ContentLoader';
16
17
 
17
- const ResourceDescription = styled.p`
18
+ const StyledResourceDescription = styled.p`
18
19
  grid-area: description;
19
20
  line-clamp: 2;
20
21
  line-height: 1em;
@@ -97,7 +98,7 @@ const StyledImage = styled(Image)`
97
98
  object-fit: cover;
98
99
  `;
99
100
 
100
- const TopicAndTitle = styled.div`
101
+ const TopicAndTitleWrapper = styled.div`
101
102
  grid-area: topicAndTitle;
102
103
  margin-top: ${spacing.xxsmall};
103
104
  `;
@@ -114,21 +115,95 @@ export interface ListResourceProps {
114
115
  tags?: string[];
115
116
  description?: string;
116
117
  menuItems?: MenuItemProps[];
118
+ isLoading?: boolean;
117
119
  }
118
120
 
119
- const ListResource = ({ link, title, tags, resourceImage, topics, description, menuItems }: ListResourceProps) => {
121
+ interface ListResourceImageProps {
122
+ resourceImage: ResourceImageProps;
123
+ loading?: boolean;
124
+ type: 'normal' | 'compact';
125
+ }
126
+
127
+ const ListResourceImage = ({ resourceImage, loading, type }: ListResourceImageProps) => {
128
+ if (!loading) {
129
+ return <StyledImage alt={resourceImage.alt} src={resourceImage.src} />;
130
+ }
131
+ return (
132
+ <ContentLoader height={'100%'} width={'100%'} viewBox={null} preserveAspectRatio="none">
133
+ <rect
134
+ x="0"
135
+ y="0"
136
+ rx="3"
137
+ ry="3"
138
+ width={type === 'compact' ? '56' : '136'}
139
+ height={type === 'compact' ? '40' : '96'}
140
+ />
141
+ </ContentLoader>
142
+ );
143
+ };
144
+
145
+ interface TopicAndTitleProps {
146
+ title: string;
147
+ topics: string[];
148
+ loading?: boolean;
149
+ }
150
+
151
+ const TopicAndTitle = ({ title, topics, loading }: TopicAndTitleProps) => {
152
+ if (loading) {
153
+ return (
154
+ <ContentLoader height={'40px'} width={'100%'} viewBox={null} preserveAspectRatio="none">
155
+ <rect x="0" y="0" rx="3" ry="3" width={'100%'} height={'16'} />
156
+ <rect x="0" y="18" rx="3" ry="3" width={'70'} height={'16'} />
157
+ <rect x="80" y="18" rx="3" ry="3" width={'70'} height={'16'} />
158
+ </ContentLoader>
159
+ );
160
+ }
161
+ return (
162
+ <>
163
+ <ResourceTitle>{title}</ResourceTitle>
164
+ <TopicList topics={topics} />
165
+ </>
166
+ );
167
+ };
168
+
169
+ interface ResourceDescriptionProps {
170
+ description?: string;
171
+ loading?: boolean;
172
+ }
173
+
174
+ const ResourceDescription = ({ description, loading }: ResourceDescriptionProps) => {
175
+ if (loading) {
176
+ return (
177
+ <ContentLoader height={'20px'} width={'100%'} viewBox={null} preserveAspectRatio="none">
178
+ <rect x="0" y="0" width="100%" height="20" />
179
+ </ContentLoader>
180
+ );
181
+ }
182
+ return <StyledResourceDescription>{description}</StyledResourceDescription>;
183
+ };
184
+
185
+ const ListResource = ({
186
+ link,
187
+ title,
188
+ tags,
189
+ resourceImage,
190
+ topics,
191
+ description,
192
+ menuItems,
193
+ isLoading = false,
194
+ }: ListResourceProps) => {
120
195
  const showDescription = description !== undefined;
196
+ const imageType = showDescription ? 'normal' : 'compact';
121
197
 
122
198
  return (
123
199
  <ResourceWrapper to={link}>
124
- <StyledImageWrapper imageSize={showDescription ? 'normal' : 'compact'}>
125
- <StyledImage alt={resourceImage.alt} src={resourceImage.src} />
200
+ <StyledImageWrapper imageSize={imageType}>
201
+ <ListResourceImage resourceImage={resourceImage} loading={isLoading} type={imageType} />
126
202
  </StyledImageWrapper>
127
- <TopicAndTitle>
128
- <ResourceTitle>{title}</ResourceTitle>
129
- <TopicList topics={topics} />
130
- </TopicAndTitle>
131
- {showDescription && <ResourceDescription>{description}</ResourceDescription>}
203
+ <TopicAndTitleWrapper>
204
+ <TopicAndTitle topics={topics} title={title} loading={isLoading} />
205
+ </TopicAndTitleWrapper>
206
+ {showDescription && <ResourceDescription description={description} loading={isLoading} />}
132
207
  <TagsandActionMenu>
133
208
  {tags && <CompressedTagList tags={tags} />}
134
209
  {menuItems && menuItems.length > 0 && <MenuButton alignRight size="small" menuItems={menuItems} />}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Copyright (c) 2022-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import React from 'react';
10
+ import { useTranslation } from 'react-i18next';
11
+ import styled from '@emotion/styled';
12
+ import { IconButton } from '@ndla/button';
13
+ import { breakpoints, colors, fonts, misc, mq, shadows, spacing } from '@ndla/core';
14
+ import { Cross } from '@ndla/icons/action';
15
+ import { Snack, useSnack } from './SnackbarProvider';
16
+
17
+ const DefaultSnackContainer = styled.div`
18
+ display: flex;
19
+ gap: ${spacing.small};
20
+ padding: ${spacing.small};
21
+ background-color: ${colors.text.primary};
22
+ color: ${colors.white};
23
+ border-radius: ${misc.borderRadius};
24
+ margin: 0 ${spacing.large};
25
+ pointer-events: all;
26
+ box-shadow: ${shadows.levitate1};
27
+ font-family: ${fonts.sans};
28
+ font-size: 18px;
29
+ align-items: center;
30
+ `;
31
+
32
+ const StyledCloseButton = styled(IconButton)`
33
+ svg {
34
+ color: ${colors.brand.greyMedium};
35
+ }
36
+ &:hover,
37
+ &:focus {
38
+ background: ${colors.brand.greyDark};
39
+ svg {
40
+ color: ${colors.brand.greyLightest};
41
+ }
42
+ }
43
+ `;
44
+
45
+ const ButtonWrapper = styled.div`
46
+ ${mq.range({ from: breakpoints.tablet })} {
47
+ gap: ${spacing.xxsmall};
48
+ }
49
+ `;
50
+
51
+ const DefaultSnack = (snack: Snack) => {
52
+ const { closable = true, icon } = snack;
53
+ const { t } = useTranslation();
54
+ const { closeSnack } = useSnack();
55
+ return (
56
+ <DefaultSnackContainer>
57
+ {icon}
58
+ {snack.content}
59
+ <ButtonWrapper>
60
+ {closable && (
61
+ <StyledCloseButton size="xsmall" outline onClick={() => closeSnack(snack)} aria-label={t('snackbar.close')}>
62
+ <Cross />
63
+ </StyledCloseButton>
64
+ )}
65
+ </ButtonWrapper>
66
+ </DefaultSnackContainer>
67
+ );
68
+ };
69
+
70
+ export default DefaultSnack;
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Copyright (c) 2022-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import React, { useCallback, useMemo, createContext, ReactNode, useState, useContext, useEffect } from 'react';
10
+ import { keyframes } from '@emotion/core';
11
+ import styled from '@emotion/styled';
12
+ import { spacing } from '@ndla/core';
13
+ import DefaultSnack from './DefaultSnackbar';
14
+
15
+ export interface Snack {
16
+ content?: ReactNode;
17
+ duration?: number;
18
+ render?: (id: string, onClose?: () => void) => ReactNode;
19
+ id: string;
20
+ icon?: ReactNode;
21
+ closable?: boolean;
22
+ }
23
+
24
+ export interface SnackContext {
25
+ addSnack: (snack: Snack) => void;
26
+ removeSnack: (id: string) => void;
27
+ clearSnacks: () => void;
28
+ closeSnack: (snack: Snack) => void;
29
+ }
30
+
31
+ const SnackbarContext = createContext<SnackContext | undefined>(undefined);
32
+
33
+ export const useSnack = () => {
34
+ const context = useContext(SnackbarContext);
35
+ if (!context) {
36
+ throw new Error('useSnack can only be used within a SnackbarProvider!');
37
+ }
38
+
39
+ return context;
40
+ };
41
+
42
+ interface Props {
43
+ children: ReactNode;
44
+ }
45
+
46
+ export const SnackbarProvider = ({ children }: Props) => {
47
+ const [snacks, setSnacks] = useState<Snack[]>([]);
48
+
49
+ const addSnack = useCallback((snack: Snack) => {
50
+ setSnacks((prev) => prev.filter((s) => s.id !== snack.id).concat(snack));
51
+ }, []);
52
+
53
+ const removeSnack = useCallback((snackId: string) => {
54
+ setSnacks((prev) => prev.filter(({ id }) => snackId !== id));
55
+ }, []);
56
+
57
+ const closeSnack = useCallback((snack: Snack) => {
58
+ setSnacks((prev) => prev.map((p) => (p.id === snack.id ? { ...p, duration: 0 } : p)));
59
+ }, []);
60
+
61
+ const clearSnacks = useCallback(() => setSnacks([]), []);
62
+
63
+ const value = useMemo(
64
+ () => ({ addSnack, removeSnack, clearSnacks, closeSnack }),
65
+ [addSnack, removeSnack, clearSnacks, closeSnack],
66
+ );
67
+
68
+ return (
69
+ <SnackbarContext.Provider value={value}>
70
+ {children}
71
+ <SnackbarContainer snacks={snacks} />
72
+ </SnackbarContext.Provider>
73
+ );
74
+ };
75
+
76
+ interface BaseSnackProps extends Snack {
77
+ children: ReactNode;
78
+ }
79
+
80
+ interface BaseSnackContainerProps {
81
+ expired: boolean;
82
+ }
83
+
84
+ const snackbarInAnimation = keyframes({
85
+ '0%': { transform: `translateY(${spacing.medium})`, opacity: 0 },
86
+ '100%': { opacity: 1 },
87
+ });
88
+
89
+ const snackbarOutAnimation = keyframes({
90
+ '0%': { opacity: 1 },
91
+ '100%': { transform: `translateY(${spacing.medium})`, opacity: 0 },
92
+ });
93
+
94
+ const BaseSnackContainer = styled.li<BaseSnackContainerProps>`
95
+ display: flex;
96
+ flex-direction: column;
97
+ align-items: center;
98
+ animation: ${(p) => (p.expired ? snackbarOutAnimation : snackbarInAnimation)} 200ms ease-in-out;
99
+ animation-fill-mode: forwards;
100
+ `;
101
+
102
+ export const BaseSnack = ({ duration = 5000, id, children }: BaseSnackProps) => {
103
+ const { removeSnack } = useSnack();
104
+ const [expired, setExpired] = useState(false);
105
+
106
+ useEffect(() => {
107
+ const timeout = setTimeout(() => setExpired(true), duration);
108
+ return () => clearTimeout(timeout);
109
+ }, [duration]);
110
+
111
+ return (
112
+ <BaseSnackContainer expired={expired} onAnimationEnd={() => expired && removeSnack(id)}>
113
+ {children}
114
+ </BaseSnackContainer>
115
+ );
116
+ };
117
+
118
+ interface SnackbarContainerProps {
119
+ snacks: Snack[];
120
+ }
121
+
122
+ const StyledSnackList = styled.ul`
123
+ position: fixed;
124
+ z-index: 99999;
125
+ display: flex;
126
+ flex-direction: column;
127
+ pointer-events: none;
128
+ margin: 0 auto;
129
+ padding: 0;
130
+ bottom: env(safe-area-inset-bottom, 40px);
131
+ right: env(safe-area-inset-right, 0px);
132
+ left: env(safe-area-inset-left, 0px);
133
+ `;
134
+
135
+ const SnackbarContainer = ({ snacks }: SnackbarContainerProps) => {
136
+ return (
137
+ <StyledSnackList aria-live="polite" role="region">
138
+ {snacks.map((snack) => (
139
+ <BaseSnack key={snack.id} {...snack}>
140
+ {snack.render?.(snack.id) ?? <DefaultSnack {...snack} />}
141
+ </BaseSnack>
142
+ ))}
143
+ </StyledSnackList>
144
+ );
145
+ };
146
+
147
+ export default SnackbarProvider;
@@ -6,8 +6,6 @@
6
6
  *
7
7
  */
8
8
 
9
- import SnackBar from './SnackBar';
10
-
11
- export type { SnackBarItem } from './SnackBar';
12
-
13
- export { SnackBar };
9
+ export type { Snack, SnackContext } from './SnackbarProvider';
10
+ export { SnackbarProvider, useSnack, BaseSnack } from './SnackbarProvider';
11
+ export { default as DefaultSnackbar } from './DefaultSnackbar';
package/src/index.ts CHANGED
@@ -240,8 +240,8 @@ export type { ListResourceProps } from './Resource';
240
240
  export type { TagType } from './TagSelector';
241
241
  export { TagSelector } from './TagSelector';
242
242
 
243
- export type { SnackBarItem } from './SnackBar';
244
- export { SnackBar } from './SnackBar';
243
+ export { SnackbarProvider, useSnack, BaseSnack, DefaultSnackbar } from './SnackBar';
244
+ export type { Snack, SnackContext } from './SnackBar';
245
245
  export { InfoBlock } from './InfoBlock';
246
246
  export { TreeStructure } from './TreeStructure';
247
247
  export type { FolderType, TreeStructureProps, TreeStructureMenuProps } from './TreeStructure';
@@ -996,6 +996,7 @@ const messages = {
996
996
  delete: 'Delete',
997
997
  edit: 'Edit',
998
998
  missingName: 'Folder name required',
999
+ folderDeleted: '"{{folderName}}" deleted',
999
1000
  },
1000
1001
  tags: '{{count}} tag',
1001
1002
  tags_plural: '{{count}} tags',
@@ -1056,8 +1057,14 @@ const messages = {
1056
1057
  removeTitle: 'Remove resource',
1057
1058
  confirmRemove: 'Are you sure you want to remove the resource from this folder?',
1058
1059
  copyLink: 'Copy link to this page',
1060
+ linkCopied: 'Copied to clipboard',
1059
1061
  addToMyNdla: 'Add to My NDLA',
1060
1062
  addedToMyNdla: 'Added to My NDLA',
1063
+ addedToFolder: 'Resource added to "{{folderName}}"',
1064
+ removedFromFolder: 'Removed from "{{folderName}}"',
1065
+ titleUpdated: 'Title updated',
1066
+ tagsUpdated: 'Tags updated',
1067
+ show: 'Show',
1061
1068
  },
1062
1069
  },
1063
1070
  snackbar: {
@@ -994,6 +994,7 @@ const messages = {
994
994
  delete: 'Slett',
995
995
  edit: 'Rediger',
996
996
  missingName: 'Mappenavn er påkrevd',
997
+ folderDeleted: '"{{folderName}}" er slettet',
997
998
  },
998
999
  tags: '{{count}} tag',
999
1000
  tags_plural: '{{count}} tags',
@@ -1054,8 +1055,14 @@ const messages = {
1054
1055
  removeTitle: 'Fjern ressurs',
1055
1056
  confirmRemove: 'Er du sikker på at du ønsker å fjerne ressursen fra denne mappen?',
1056
1057
  copyLink: 'Kopier lenke til siden',
1058
+ linkCopied: 'Kopiert til utklippstavle',
1057
1059
  addToMyNdla: 'Legg i Min NDLA',
1058
1060
  addedToMyNdla: 'Lagt i Min NDLA',
1061
+ addedToFolder: 'Ressurs er lagt i "{{folderName}}"',
1062
+ removedFromFolder: 'Fjernet fra "{{folderName}}"',
1063
+ titleUpdated: 'Tittel oppdatert',
1064
+ tagsUpdated: 'Tags oppdatert',
1065
+ show: 'Vis',
1059
1066
  },
1060
1067
  },
1061
1068
  snackbar: {
@@ -995,6 +995,7 @@ const messages = {
995
995
  delete: 'Slett',
996
996
  edit: 'Rediger',
997
997
  missingName: 'Mappenavn er påkrevd',
998
+ folderDeleted: '"{{folderName}}" er sletta',
998
999
  },
999
1000
  tags: '{{count}} tag',
1000
1001
  tags_plural: '{{count}} tags',
@@ -1054,8 +1055,14 @@ const messages = {
1054
1055
  removeTitle: 'Fjern ressurs',
1055
1056
  confirmRemove: 'Er du sikker på at du ønsker å fjerne ressursen frå denne mappen?',
1056
1057
  copyLink: 'Kopier lenke til sida',
1058
+ linkCopied: 'Kopiert til utklippstavla',
1057
1059
  addToMyNdla: 'Legg i Min NDLA',
1058
1060
  addedToMyNdla: 'Lagt i Min NDLA',
1061
+ addedToFolder: 'Ressurs er lagt i "{{folderName}}"',
1062
+ removedFromFolder: 'Fjernet fra "{{folderName}}"',
1063
+ titleUpdated: 'Tittel oppdatert',
1064
+ tagsUpdated: 'Tags oppdatert',
1065
+ show: 'Vis',
1059
1066
  },
1060
1067
  },
1061
1068
  snackbar: {
@@ -994,6 +994,7 @@ const messages = {
994
994
  delete: 'Slett',
995
995
  edit: 'Rediger',
996
996
  missingName: 'Mappenavn er påkrevd',
997
+ folderDeleted: '"{{folderName}}" er sletta',
997
998
  },
998
999
  tags: '{{count}} tag',
999
1000
  tags_plural: '{{count}} tags',
@@ -1054,8 +1055,14 @@ const messages = {
1054
1055
  removeTitle: 'Fjern ressurs',
1055
1056
  confirmRemove: 'Er du sikker på at du ønsker å fjerne ressursen frå denne mappen?',
1056
1057
  copyLink: 'Kopier lenke til siden',
1058
+ linkCopied: 'Kopiert til utklippstavla',
1057
1059
  addToMyNdla: 'Legg i Min NDLA',
1058
1060
  addedToMyNdla: 'Lagt i Min NDLA',
1061
+ addedToFolder: 'Ressurs er lagt i "{{folderName}}"',
1062
+ removedFromFolder: 'Fjernet fra "{{folderName}}"',
1063
+ titleUpdated: 'Tittel oppdatert',
1064
+ tagsUpdated: 'Tags oppdatert',
1065
+ show: 'Vis',
1059
1066
  },
1060
1067
  },
1061
1068
  snackbar: {
@@ -994,6 +994,7 @@ const messages = {
994
994
  delete: 'Slett',
995
995
  edit: 'Rediger',
996
996
  missingName: 'Mappenavn er påkrevd',
997
+ folderDeleted: '"{{folderName}}" er sletta',
997
998
  },
998
999
  tags: '{{count}} tag',
999
1000
  tags_plural: '{{count}} tags',
@@ -1054,8 +1055,14 @@ const messages = {
1054
1055
  removeTitle: 'Fjern ressurs',
1055
1056
  confirmRemove: 'Er du sikker på at du ønsker å fjerne ressursen frå denne mappen?',
1056
1057
  copyLink: 'Kopier lenke til siden',
1058
+ linkCopied: 'Kopiert til utklippstavla',
1057
1059
  addToMyNdla: 'Legg i Min NDLA',
1058
1060
  addedToMyNdla: 'Lagt i Min NDLA',
1061
+ addedToFolder: 'Ressurs er lagt i "{{folderName}}"',
1062
+ removedFromFolder: 'Fjernet fra "{{folderName}}"',
1063
+ titleUpdated: 'Tittel oppdatert',
1064
+ tagsUpdated: 'Tags oppdatert',
1065
+ show: 'Vis',
1059
1066
  },
1060
1067
  },
1061
1068
  snackbar: {