@hocuspocus/provider 3.0.0-rc.0 → 3.0.6-rc.0
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.
- package/dist/hocuspocus-provider.cjs +773 -11306
- package/dist/hocuspocus-provider.cjs.map +1 -1
- package/dist/hocuspocus-provider.esm.js +753 -11303
- package/dist/hocuspocus-provider.esm.js.map +1 -1
- package/dist/packages/common/src/index.d.ts +4 -4
- package/dist/packages/extension-database/src/index.d.ts +1 -1
- package/dist/packages/extension-logger/src/index.d.ts +1 -1
- package/dist/packages/extension-redis/src/Redis.d.ts +6 -2
- package/dist/packages/extension-redis/src/index.d.ts +1 -1
- package/dist/packages/extension-sqlite/src/index.d.ts +1 -1
- package/dist/packages/provider/src/HocuspocusProvider.d.ts +14 -36
- package/dist/packages/provider/src/HocuspocusProviderWebsocket.d.ts +4 -8
- package/dist/packages/provider/src/IncomingMessage.d.ts +2 -2
- package/dist/packages/provider/src/MessageReceiver.d.ts +2 -4
- package/dist/packages/provider/src/MessageSender.d.ts +2 -2
- package/dist/packages/provider/src/OutgoingMessage.d.ts +2 -2
- package/dist/packages/provider/src/OutgoingMessages/AuthenticationMessage.d.ts +3 -3
- package/dist/packages/provider/src/OutgoingMessages/AwarenessMessage.d.ts +3 -3
- package/dist/packages/provider/src/OutgoingMessages/CloseMessage.d.ts +3 -3
- package/dist/packages/provider/src/OutgoingMessages/QueryAwarenessMessage.d.ts +3 -3
- package/dist/packages/provider/src/OutgoingMessages/StatelessMessage.d.ts +3 -3
- package/dist/packages/provider/src/OutgoingMessages/SyncStepOneMessage.d.ts +3 -3
- package/dist/packages/provider/src/OutgoingMessages/SyncStepTwoMessage.d.ts +3 -3
- package/dist/packages/provider/src/OutgoingMessages/UpdateMessage.d.ts +3 -3
- package/dist/packages/provider/src/index.d.ts +3 -5
- package/dist/packages/provider/src/types.d.ts +50 -10
- package/dist/packages/server/src/ClientConnection.d.ts +16 -8
- package/dist/packages/server/src/Connection.d.ts +14 -20
- package/dist/packages/server/src/DirectConnection.d.ts +4 -4
- package/dist/packages/server/src/Document.d.ts +2 -6
- package/dist/packages/server/src/Hocuspocus.d.ts +6 -15
- package/dist/packages/server/src/IncomingMessage.d.ts +4 -3
- package/dist/packages/server/src/MessageReceiver.d.ts +6 -8
- package/dist/packages/server/src/OutgoingMessage.d.ts +2 -1
- package/dist/packages/server/src/Server.d.ts +3 -3
- package/dist/packages/server/src/index.d.ts +9 -10
- package/dist/packages/server/src/types.d.ts +41 -13
- package/dist/packages/transformer/src/Prosemirror.d.ts +1 -1
- package/dist/packages/transformer/src/Tiptap.d.ts +1 -1
- package/dist/packages/transformer/src/index.d.ts +3 -3
- package/dist/playground/frontend/app/SocketContext.d.ts +2 -0
- package/dist/playground/frontend/next.config.d.ts +3 -0
- package/dist/tests/utils/index.d.ts +9 -9
- package/dist/tests/utils/newHocuspocusProvider.d.ts +2 -2
- package/package.json +3 -3
- package/src/HocuspocusProvider.ts +88 -210
- package/src/HocuspocusProviderWebsocket.ts +23 -73
- package/src/IncomingMessage.ts +1 -1
- package/src/MessageReceiver.ts +22 -21
- package/src/MessageSender.ts +1 -1
- package/src/OutgoingMessage.ts +1 -1
- package/src/OutgoingMessages/AuthenticationMessage.ts +3 -3
- package/src/OutgoingMessages/AwarenessMessage.ts +3 -3
- package/src/OutgoingMessages/CloseMessage.ts +3 -3
- package/src/OutgoingMessages/QueryAwarenessMessage.ts +3 -3
- package/src/OutgoingMessages/StatelessMessage.ts +3 -3
- package/src/OutgoingMessages/SyncStepOneMessage.ts +3 -3
- package/src/OutgoingMessages/SyncStepTwoMessage.ts +3 -3
- package/src/OutgoingMessages/UpdateMessage.ts +3 -3
- package/src/index.ts +3 -5
- package/src/types.ts +56 -10
- package/dist/packages/provider/src/TiptapCollabProvider.d.ts +0 -64
- package/dist/packages/provider/src/TiptapCollabProviderWebsocket.d.ts +0 -20
- package/dist/packages/server/src/Debugger.d.ts +0 -14
- package/dist/playground/frontend/vite.config.d.ts +0 -2
- package/dist/tests/server/getMessageLogs.d.ts +0 -1
- package/dist/tests/server/requiresAuthentication.d.ts +0 -1
- package/src/TiptapCollabProvider.ts +0 -321
- package/src/TiptapCollabProviderWebsocket.ts +0 -39
- /package/dist/{playground/frontend/src/main.d.ts → tests/server/beforeSync.d.ts} +0 -0
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
import type { AbstractType, YArrayEvent } from 'yjs'
|
|
2
|
-
import * as Y from 'yjs'
|
|
3
|
-
import { uuidv4 } from 'lib0/random'
|
|
4
|
-
import type {
|
|
5
|
-
HocuspocusProviderConfiguration} from './HocuspocusProvider.js'
|
|
6
|
-
import {
|
|
7
|
-
HocuspocusProvider,
|
|
8
|
-
} from './HocuspocusProvider.js'
|
|
9
|
-
|
|
10
|
-
import { TiptapCollabProviderWebsocket } from './TiptapCollabProviderWebsocket.js'
|
|
11
|
-
import type {
|
|
12
|
-
TCollabComment, TCollabThread, THistoryVersion,
|
|
13
|
-
} from './types.js'
|
|
14
|
-
|
|
15
|
-
export type TiptapCollabProviderConfiguration =
|
|
16
|
-
Required<Pick<HocuspocusProviderConfiguration, 'name'>> &
|
|
17
|
-
Partial<HocuspocusProviderConfiguration> &
|
|
18
|
-
(Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> |
|
|
19
|
-
Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>>|
|
|
20
|
-
Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>) &
|
|
21
|
-
Pick<AdditionalTiptapCollabProviderConfiguration, 'user'>
|
|
22
|
-
|
|
23
|
-
export interface AdditionalTiptapCollabProviderConfiguration {
|
|
24
|
-
/**
|
|
25
|
-
* A Hocuspocus Cloud App ID, get one here: https://cloud.tiptap.dev
|
|
26
|
-
*/
|
|
27
|
-
appId?: string,
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* If you are using the on-premise version of TiptapCollab, put your baseUrl here (e.g. https://collab.yourdomain.com)
|
|
31
|
-
*/
|
|
32
|
-
baseUrl?: string
|
|
33
|
-
|
|
34
|
-
websocketProvider?: TiptapCollabProviderWebsocket
|
|
35
|
-
|
|
36
|
-
user?: string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class TiptapCollabProvider extends HocuspocusProvider {
|
|
40
|
-
tiptapCollabConfigurationPrefix = '__tiptapcollab__'
|
|
41
|
-
|
|
42
|
-
userData?: Y.PermanentUserData
|
|
43
|
-
|
|
44
|
-
constructor(configuration: TiptapCollabProviderConfiguration) {
|
|
45
|
-
if (!configuration.websocketProvider) {
|
|
46
|
-
configuration.websocketProvider = new TiptapCollabProviderWebsocket({ appId: (configuration as Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>>).appId, baseUrl: (configuration as Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>).baseUrl })
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (!configuration.token) {
|
|
50
|
-
configuration.token = 'notoken' // need to send a token anyway (which will be ignored)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
super(configuration as HocuspocusProviderConfiguration)
|
|
54
|
-
|
|
55
|
-
if (configuration.user) {
|
|
56
|
-
this.userData = new Y.PermanentUserData(this.document, this.document.getMap('__tiptapcollab__users'))
|
|
57
|
-
this.userData.setUserMapping(this.document, this.document.clientID, configuration.user)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
63
|
-
*/
|
|
64
|
-
createVersion(name?: string) {
|
|
65
|
-
return this.sendStateless(JSON.stringify({ action: 'version.create', name }))
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
70
|
-
*/
|
|
71
|
-
revertToVersion(targetVersion: number) {
|
|
72
|
-
return this.sendStateless(JSON.stringify({ action: 'document.revert', version: targetVersion }))
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
77
|
-
*
|
|
78
|
-
* The server will reply with a stateless message (THistoryVersionPreviewEvent)
|
|
79
|
-
*/
|
|
80
|
-
previewVersion(targetVersion: number) {
|
|
81
|
-
return this.sendStateless(JSON.stringify({ action: 'version.preview', version: targetVersion }))
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
|
|
86
|
-
*/
|
|
87
|
-
getVersions(): THistoryVersion[] {
|
|
88
|
-
return this.configuration.document.getArray<THistoryVersion>(`${this.tiptapCollabConfigurationPrefix}versions`).toArray()
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
watchVersions(callback: Parameters<AbstractType<YArrayEvent<THistoryVersion>>['observe']>[0]) {
|
|
92
|
-
return this.configuration.document.getArray<THistoryVersion>('__tiptapcollab__versions').observe(callback)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
unwatchVersions(callback: Parameters<AbstractType<YArrayEvent<THistoryVersion>>['unobserve']>[0]) {
|
|
96
|
-
return this.configuration.document.getArray<THistoryVersion>('__tiptapcollab__versions').unobserve(callback)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
isAutoVersioning(): boolean {
|
|
100
|
-
return !!this.configuration.document.getMap<number>(`${this.tiptapCollabConfigurationPrefix}config`).get('autoVersioning')
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
enableAutoVersioning() {
|
|
104
|
-
return this.configuration.document.getMap<number>(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 1)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
disableAutoVersioning() {
|
|
108
|
-
return this.configuration.document.getMap<number>(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 0)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private getYThreads() {
|
|
112
|
-
return this.configuration.document.getArray<Y.Map<any>>(`${this.tiptapCollabConfigurationPrefix}threads`)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
getThreads<Data, CommentData>(): TCollabThread<Data, CommentData>[] {
|
|
116
|
-
return this.getYThreads().toJSON() as TCollabThread<Data, CommentData>[]
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private getThreadIndex(id: string): number | null {
|
|
120
|
-
let index = null
|
|
121
|
-
|
|
122
|
-
let i = 0
|
|
123
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
124
|
-
for (const thread of this.getThreads()) {
|
|
125
|
-
if (thread.id === id) {
|
|
126
|
-
index = i
|
|
127
|
-
break
|
|
128
|
-
}
|
|
129
|
-
i += 1
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return index
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
getThread<Data, CommentData>(id: string): TCollabThread<Data, CommentData> | null {
|
|
136
|
-
const index = this.getThreadIndex(id)
|
|
137
|
-
|
|
138
|
-
if (index === null) {
|
|
139
|
-
return null
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return this.getYThreads().get(index).toJSON() as TCollabThread<Data, CommentData>
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private getYThread(id: string) {
|
|
146
|
-
const index = this.getThreadIndex(id)
|
|
147
|
-
|
|
148
|
-
if (index === null) {
|
|
149
|
-
return null
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return this.getYThreads().get(index)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'comments'>) {
|
|
156
|
-
let createdThread: TCollabThread = {} as TCollabThread
|
|
157
|
-
|
|
158
|
-
this.document.transact(() => {
|
|
159
|
-
const thread = new Y.Map()
|
|
160
|
-
thread.set('id', uuidv4())
|
|
161
|
-
thread.set('createdAt', (new Date()).toISOString())
|
|
162
|
-
thread.set('comments', new Y.Array())
|
|
163
|
-
|
|
164
|
-
this.getYThreads().push([thread])
|
|
165
|
-
createdThread = this.updateThread(String(thread.get('id')), data)
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
return createdThread
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
updateThread(id: TCollabThread['id'], data: Partial<Pick<TCollabThread, 'data'> & {
|
|
172
|
-
resolvedAt: TCollabThread['resolvedAt'] | null
|
|
173
|
-
}>) {
|
|
174
|
-
let updatedThread: TCollabThread = {} as TCollabThread
|
|
175
|
-
|
|
176
|
-
this.document.transact(() => {
|
|
177
|
-
const thread = this.getYThread(id)
|
|
178
|
-
|
|
179
|
-
if (thread === null) {
|
|
180
|
-
return null
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
thread.set('updatedAt', (new Date()).toISOString())
|
|
184
|
-
|
|
185
|
-
if (data.data) {
|
|
186
|
-
thread.set('data', data.data)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (data.resolvedAt || data.resolvedAt === null) {
|
|
190
|
-
thread.set('resolvedAt', data.resolvedAt)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
updatedThread = thread.toJSON() as TCollabThread
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
return updatedThread
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
deleteThread(id: TCollabThread['id']) {
|
|
200
|
-
const index = this.getThreadIndex(id)
|
|
201
|
-
|
|
202
|
-
if (index === null) {
|
|
203
|
-
return
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
this.getYThreads().delete(index, 1)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
getThreadComments(threadId: TCollabThread['id']): TCollabComment[] | null {
|
|
210
|
-
const index = this.getThreadIndex(threadId)
|
|
211
|
-
|
|
212
|
-
if (index === null) {
|
|
213
|
-
return null
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return this.getThread(threadId)?.comments ?? []
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabComment | null {
|
|
220
|
-
const index = this.getThreadIndex(threadId)
|
|
221
|
-
|
|
222
|
-
if (index === null) {
|
|
223
|
-
return null
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return this.getThread(threadId)?.comments.find(comment => comment.id === commentId) ?? null
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
addComment(threadId: TCollabThread['id'], data: Omit<TCollabComment, 'id' | 'updatedAt' | 'createdAt'>) {
|
|
230
|
-
let updatedThread: TCollabThread = {} as TCollabThread
|
|
231
|
-
|
|
232
|
-
this.document.transact(() => {
|
|
233
|
-
const thread = this.getYThread(threadId)
|
|
234
|
-
|
|
235
|
-
if (thread === null) return null
|
|
236
|
-
|
|
237
|
-
const commentMap = new Y.Map()
|
|
238
|
-
commentMap.set('id', uuidv4())
|
|
239
|
-
commentMap.set('createdAt', (new Date()).toISOString())
|
|
240
|
-
thread.get('comments').push([commentMap])
|
|
241
|
-
|
|
242
|
-
this.updateComment(threadId, String(commentMap.get('id')), data)
|
|
243
|
-
|
|
244
|
-
updatedThread = thread.toJSON() as TCollabThread
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
return updatedThread
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
updateComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], data: Partial<Pick<TCollabComment, 'data' | 'content'>>) {
|
|
251
|
-
let updatedThread: TCollabThread = {} as TCollabThread
|
|
252
|
-
|
|
253
|
-
this.document.transact(() => {
|
|
254
|
-
const thread = this.getYThread(threadId)
|
|
255
|
-
|
|
256
|
-
if (thread === null) return null
|
|
257
|
-
|
|
258
|
-
let comment = null
|
|
259
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
260
|
-
for (const c of thread.get('comments')) {
|
|
261
|
-
if (c.get('id') === commentId) {
|
|
262
|
-
comment = c
|
|
263
|
-
break
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (comment === null) return null
|
|
268
|
-
|
|
269
|
-
comment.set('updatedAt', (new Date()).toISOString())
|
|
270
|
-
|
|
271
|
-
if (data.data) {
|
|
272
|
-
comment.set('data', data.data)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (data.content) {
|
|
276
|
-
comment.set('content', data.content)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
updatedThread = thread.toJSON() as TCollabThread
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
return updatedThread
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']) {
|
|
286
|
-
const thread = this.getYThread(threadId)
|
|
287
|
-
|
|
288
|
-
if (thread === null) return null
|
|
289
|
-
|
|
290
|
-
let commentIndex = 0
|
|
291
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
292
|
-
for (const c of thread.get('comments')) {
|
|
293
|
-
if (c.get('id') === commentId) {
|
|
294
|
-
break
|
|
295
|
-
}
|
|
296
|
-
commentIndex += 1
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// if the first comment of a thread is deleted we also
|
|
300
|
-
// delete the thread itself as the source comment is gone
|
|
301
|
-
if (commentIndex === 0) {
|
|
302
|
-
this.deleteThread(threadId)
|
|
303
|
-
return
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (commentIndex > 0) {
|
|
307
|
-
thread.get('comments').delete(commentIndex)
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return thread.toJSON() as TCollabThread
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
watchThreads(callback: () => void) {
|
|
314
|
-
this.getYThreads().observeDeep(callback)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
unwatchThreads(callback: () => void) {
|
|
318
|
-
this.getYThreads().unobserveDeep(callback)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CompleteHocuspocusProviderWebsocketConfiguration, HocuspocusProviderWebsocketConfiguration} from './HocuspocusProviderWebsocket.js'
|
|
3
|
-
import {
|
|
4
|
-
HocuspocusProviderWebsocket,
|
|
5
|
-
} from './HocuspocusProviderWebsocket.js'
|
|
6
|
-
|
|
7
|
-
export type TiptapCollabProviderWebsocketConfiguration =
|
|
8
|
-
Partial<CompleteHocuspocusProviderWebsocketConfiguration> &
|
|
9
|
-
AdditionalTiptapCollabProviderWebsocketConfiguration
|
|
10
|
-
|
|
11
|
-
export interface AdditionalTiptapCollabProviderWebsocketConfiguration {
|
|
12
|
-
/**
|
|
13
|
-
* A Hocuspocus Cloud App ID, get one here: https://cloud.tiptap.dev
|
|
14
|
-
*/
|
|
15
|
-
appId?: string,
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* If you are using the on-premise version of TiptapCollab, put your baseUrl here (e.g. https://collab.yourdomain.com)
|
|
19
|
-
*/
|
|
20
|
-
baseUrl?: string
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Only fill this if you are using Tiptap Collab HA.
|
|
24
|
-
*/
|
|
25
|
-
shardKey?: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class TiptapCollabProviderWebsocket extends HocuspocusProviderWebsocket {
|
|
29
|
-
constructor(configuration: TiptapCollabProviderWebsocketConfiguration) {
|
|
30
|
-
let url = configuration.baseUrl ?? `wss://${configuration.appId}.collab.tiptap.cloud`
|
|
31
|
-
|
|
32
|
-
if (configuration.shardKey) {
|
|
33
|
-
url += url.includes('?') ? '&' : '?'
|
|
34
|
-
url += `shard=${configuration.shardKey}`
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
super({ ...configuration as HocuspocusProviderWebsocketConfiguration, url })
|
|
38
|
-
}
|
|
39
|
-
}
|
|
File without changes
|