Move services to public (#6156)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2024-07-28 14:55:43 +07:00
committed by Andrey Sobolev
parent 9aad4a4561
commit ddecae80dd
578 changed files with 109084 additions and 390 deletions
@@ -0,0 +1,62 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Label } from '@hcengineering/ui'
import { Diff, DiffFile, DiffFileId, DiffViewMode } from '@hcengineering/diffview'
import DiffViewModeDropdown from './DiffViewModeDropdown.svelte'
import FileDiffView from './FileDiffView.svelte'
import { parseDiff } from '../parser'
import diffview from '../plugin'
export let patch: Diff
export let viewed: DiffFileId[]
let mode: DiffViewMode = getCurrentMode()
function getCurrentMode (): DiffViewMode {
return (localStorage.getItem('diffview.mode') as DiffViewMode) ?? 'unified'
}
function saveMode (value: DiffViewMode) {
localStorage.setItem('diffview.mode', value)
mode = value
}
function isFileViewed (diffFile: DiffFile): boolean {
const { fileName, sha } = diffFile
return viewed.some((file) => file.fileName === fileName && file.sha === sha)
}
$: diffFiles = parseDiff(patch ?? '')
</script>
<div class="flex-row-center justify-end gap-2 mb-1">
<span class="overflow-label"><Label label={diffview.string.ViewMode} /></span>
<DiffViewModeDropdown
kind={'regular'}
size={'medium'}
label={diffview.string.ViewMode}
bind:selected={mode}
on:selected={({ detail }) => {
saveMode(detail)
}}
/>
</div>
{#each diffFiles as diffFile}
{@const fileViewed = isFileViewed(diffFile)}
<FileDiffView file={diffFile} viewed={fileViewed} {mode} on:change />
{/each}
@@ -0,0 +1,38 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { ButtonKind, ButtonSize, DropdownIntlItem, DropdownLabelsIntl } from '@hcengineering/ui'
import { DiffViewMode } from '@hcengineering/diffview'
import diffview from '../plugin'
export let selected: DiffViewMode | undefined
export let kind: ButtonKind = 'link-bordered'
export let size: ButtonSize = 'small'
export let label = diffview.string.ViewMode
const viewModeDropdownItems: DropdownIntlItem[] = [
{
id: 'unified',
label: diffview.string.Unified
},
{
id: 'split',
label: diffview.string.Split
}
]
</script>
<DropdownLabelsIntl {kind} {size} {label} items={viewModeDropdownItems} width={'6rem'} bind:selected on:selected />
@@ -0,0 +1,228 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Loading, themeStore } from '@hcengineering/ui'
import { DiffFile, DiffLine, DiffLineType, DiffViewMode } from '@hcengineering/diffview'
import { DiffLineRenderResult, RenderOptions, renderHunk } from '../highlight'
export let file: DiffFile
export let mode: DiffViewMode
export let tabSize = 4
const options: RenderOptions = {
syntaxHighlight: {
language: file.language ?? ''
}
}
function codeLineClass (line: DiffLine): string {
return `line-${line.type}`
}
$: highlighted = file.hunks.map((hunk) => renderHunk(hunk, options))
function prepareLinesForUnifiedView (lines: DiffLineRenderResult[]): DiffLine[] {
return lines.map(({ before, after }) => (after.type !== DiffLineType.EMPTY ? after : before))
}
function prepareLinesForSplitView (lines: DiffLineRenderResult[]): DiffLineRenderResult[] {
const before: DiffLine[] = []
const after: DiffLine[] = []
let moved: DiffLine[] = []
for (const line of lines) {
before.push(line.before)
if (line.after.type === 'insert') {
after.push(line.after)
} else if (line.after.type === 'context') {
after.push(...moved)
moved = []
after.push(line.after)
} else {
moved.push(line.after)
}
}
after.push(...moved)
const result: DiffLineRenderResult[] = []
while (before.length > 0 && after.length > 0) {
const beforeLine = before.shift()
const afterLine = after.shift()
if (beforeLine && afterLine) {
result.push({ before: beforeLine, after: afterLine })
}
}
return result
}
</script>
<div
class="highlight-container"
class:highlight-container-dark={$themeStore.dark}
class:highlight-container-light={!$themeStore.dark}
>
{#if highlighted === undefined}
<Loading />
{:else}
<table class="diff-table diff-table-{mode} tab-size" data-tab-size={tabSize}>
<colgroup>
{#if mode === 'unified'}
<col class="num-col" />
<col class="num-col" />
<col class="code-col" />
{:else}
<col class="num-col" />
<col class="code-col" />
<col class="num-col" />
<col class="code-col" />
{/if}
</colgroup>
<tbody>
{#each highlighted as hhunk}
{@const hunk = hhunk.hunk}
{#if mode === 'unified'}
{@const lines = prepareLinesForUnifiedView(hhunk.lines)}
<tr>
<td class="num-line line-header" />
<td class="num-line line-header" />
<td class="code-line line-header">{hunk.header}</td>
</tr>
{#each lines as line}
{@const lineClass = codeLineClass(line)}
<tr>
<td class="num-line {lineClass}">{line.oldNumber ?? ''}</td>
<td class="num-line {lineClass}">{line.newNumber ?? ''}</td>
<td class="code-line select-text {lineClass}" data-code-marker={line.prefix}>
{@html line.content}
</td>
</tr>
{/each}
{:else}
{@const lines = prepareLinesForSplitView(hhunk.lines)}
<tr>
<td class="num-line line-header" />
<td class="code-line line-header" colspan="3">{hunk.header}</td>
</tr>
{#each lines as line}
{@const before = line.before}
{@const after = line.after}
{@const beforeLineClass = codeLineClass(before)}
{@const afterLineClass = codeLineClass(after)}
<tr>
<td class="num-line {beforeLineClass}">{before.oldNumber ?? ''}</td>
<td class="code-line select-text {beforeLineClass}" data-code-marker={before.prefix}>
{@html before.content}
</td>
<td class="num-line {afterLineClass}">{after.newNumber ?? ''}</td>
<td class="code-line select-text {afterLineClass}" data-code-marker={after.prefix}>
{@html after.content}
</td>
</tr>
{/each}
{/if}
{/each}
</tbody>
</table>
{/if}
</div>
<style lang="scss">
.highlight-container-light :global {
@import './theme/github.scss';
}
.highlight-container-dark :global {
@import './theme/github-dark.scss';
}
.diff-table {
font-family: var(--mono-font);
font-size: 0.8125rem;
width: 100%;
&.tab-size {
tab-size: attr(data-tab-size);
}
&.diff-table-split {
table-layout: fixed;
}
}
.diff-table-split .code-line + .num-line {
border-left: 1px solid var(--theme-divider-color);
}
td {
&.code-line {
border-left: 1px solid var(--theme-divider-color);
}
&.line-header {
background-color: var(--theme-diffview-block-header-color);
padding-top: 0.25rem;
padding-bottom: 0.25rem;
white-space: pre-wrap;
}
&.line-insert {
background-color: var(--theme-diffview-insert-line-color);
}
&.line-delete {
background-color: var(--theme-diffview-delete-line-color);
}
&.line-empty {
background-color: var(--theme-diffview-empty-line-color);
}
}
.num-col {
width: 1px;
min-width: 3.5rem;
}
.num-line {
padding: 0 0.5rem;
text-align: right;
vertical-align: top;
text-overflow: ellipsis;
white-space: nowrap;
position: relative;
}
.code-line {
position: relative;
color: var(--theme-diffview-line-color);
padding: 0 1.5rem;
vertical-align: top;
white-space: pre-wrap;
word-wrap: anywhere;
}
.code-line::before {
content: attr(data-code-marker);
position: absolute;
left: 0.5rem;
}
</style>
@@ -0,0 +1,121 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { copyTextToClipboard } from '@hcengineering/presentation'
import { Button, Chevron, IconCheck, IconCopy } from '@hcengineering/ui'
import diffview, { DiffFile } from '@hcengineering/diffview'
import { formatFileName, isDevNullName } from '../utils'
export let file: DiffFile
export let expanded = true
export let viewed = false
export let showViewed = true
export let showExpand = true
export let showChanges = true
const dispatch = createEventDispatcher()
async function copyFileNameToClipboard (): Promise<void> {
const { oldName, newName } = file
const name = isDevNullName(newName) ? newName : oldName
await copyTextToClipboard(name)
}
</script>
<div class="w-full flex-between">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="file-header-content flex-row-center h-8">
{#if showExpand}
<Button
width="min-content"
kind="ghost"
padding={'0 .375rem'}
noFocus
on:click={() => {
dispatch('expand')
}}
>
<svelte:fragment slot="content">
<Chevron size={'small'} {expanded} outline fill={'var(--caption-color)'} />
</svelte:fragment>
</Button>
{/if}
<div class="file-info overflow-label ml-1 mr-2">
<span class="file-name">{formatFileName(file)}</span>
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="hover-trans" on:click={() => copyFileNameToClipboard()}>
<IconCopy size={'small'} />
</div>
</div>
<div class="file-header-actions flex-row-center flex-no-shrink h-8">
{#if showChanges}
<div class="file-stats flex pl-2 pr-2">
<div class="lines-added flex">
<span>+</span>
<span>{file.stats.addedLines}</span>
</div>
<div class="lines-deleted flex">
<span>+</span>
<span>{file.stats.deletedLines}</span>
</div>
</div>
{/if}
{#if showViewed}
<div class="mr-2">
<Button
icon={IconCheck}
kind={'ghost'}
showTooltip={{ label: diffview.string.Viewed }}
highlight={viewed}
noFocus
on:click={() => {
dispatch('viewed', !viewed)
}}
/>
</div>
{/if}
</div>
</div>
<style lang="scss">
.file-info {
font-weight: 600;
direction: rtl;
text-align: left;
}
.file-stats {
display: inline-flex;
font-weight: 500;
.lines-added {
padding: 0 0.25rem;
color: var(--theme-diffview-insert-color);
}
.lines-deleted {
padding: 0 0.25rem;
color: var(--theme-diffview-delete-color);
}
}
</style>
@@ -0,0 +1,151 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { IntlString } from '@hcengineering/platform'
import { Button, Label } from '@hcengineering/ui'
import { DiffFile, DiffViewMode } from '@hcengineering/diffview'
import FileDiffContent from './FileDiffContent.svelte'
import FileDiffHeader from './FileDiffHeader.svelte'
import diffview from '../plugin'
export let file: DiffFile
export let mode: DiffViewMode
export let viewed = false
export let showViewed = true
export let showExpand = true
export let showChanges = true
export let tabSize = 4
export let diffRenderLimit = 200
const dispatch = createEventDispatcher()
let expanded = !viewed
let hideDiffLabel = getHideDiffLabel(file)
function getHideDiffLabel (file: DiffFile): IntlString | undefined {
if (diffRenderLimit >= 0 && file.stats.addedLines + file.stats.addedLines > diffRenderLimit) {
return diffview.string.LargeDiffsAreHidden
}
if (file.diffType === 'delete') {
return diffview.string.FileWasDeleted
}
}
function getNoChangesLabel (file: DiffFile): IntlString {
if (file.hunks.length === 0) {
if (file.isTooBig === true) {
return diffview.string.FileIsTooLarge
}
if (file.diffType === 'rename') {
return diffview.string.FileWasRenamed
}
}
return diffview.string.NoChanges
}
function showDiff (): void {
hideDiffLabel = undefined
}
</script>
<div class="diff-file">
<!-- Header -->
<div class="file-header sticky-file-header flex-between" class:expanded>
<FileDiffHeader
{file}
{expanded}
{viewed}
{showViewed}
{showExpand}
{showChanges}
on:expand={() => {
expanded = !expanded
dispatch('expanded', expanded)
}}
on:viewed={(evt) => {
viewed = evt.detail
expanded = !viewed
dispatch('change', { fileName: file.fileName, sha: file.sha, viewed })
}}
/>
</div>
<!-- Content -->
{#if expanded}
<div class="file-content w-full">
{#if file.hunks.length === 0}
{@const label = getNoChangesLabel(file)}
<div class="p-2">
<span class="overflow-label"><Label {label} /></span>
</div>
{/if}
{#if hideDiffLabel}
<div class="pt-6 pb-6 flex-col-center">
<Button
label={diffview.string.ShowDiff}
kind={'link'}
accent
noFocus
on:click={() => {
showDiff()
}}
size={'small'}
/>
<span class="overflow-label"><Label label={hideDiffLabel} /></span>
</div>
{:else}
<FileDiffContent {file} {mode} {tabSize} />
{/if}
</div>
{/if}
</div>
<style lang="scss">
.diff-file {
margin-bottom: 1rem;
}
.file-header {
padding: 0.25rem 0.5rem;
border: 1px solid var(--theme-divider-color);
border-radius: 0.25rem;
background-color: var(--theme-comp-header-color);
overflow-y: hidden;
&.expanded {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
.sticky-file-header {
position: sticky;
top: 0;
z-index: 1;
}
.file-content {
border: 1px solid var(--theme-divider-color);
border-top: 0;
border-bottom-left-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
overflow-y: hidden;
}
</style>
@@ -0,0 +1,48 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { parseDiff } from '../parser'
import FileDiffView from './FileDiffView.svelte'
export let patch: string
export let fileName: string
export let expandable = false
export let expanded = true
export let onExpand: ((value: boolean) => void) | undefined
$: prefix = `
diff --git a/${fileName} b/${fileName}
index 3aa8590..6b64999 100644
--- a/${fileName}
+++ b/${fileName}
`
$: diffFiles = parseDiff(prefix + patch ?? '')
</script>
{#each diffFiles as diffFile}
<FileDiffView
file={diffFile}
viewed={!expanded}
mode={'unified'}
showExpand={expandable}
showViewed={false}
showChanges={false}
on:change
on:expanded={(evt) => {
onExpand?.(evt.detail)
}}
/>
{/each}
@@ -0,0 +1,94 @@
pre code.hljs {
display: block;
overflow-x: auto;
padding: 1em;
}
code.hljs {
padding: 3px 5px;
} /*!
Theme: GitHub Dark
Description: Dark theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-dark
Current colors taken from GitHub's CSS
*/
.hljs {
color: #c9d1d9;
// background: #0d1117;
}
.hljs-doctag,
.hljs-keyword,
.hljs-meta .hljs-keyword,
.hljs-template-tag,
.hljs-template-variable,
.hljs-type,
.hljs-variable.language_ {
color: #ff7b72;
}
.hljs-title,
.hljs-title.class_,
.hljs-title.class_.inherited__,
.hljs-title.function_ {
color: #d2a8ff;
}
.hljs-attr,
.hljs-attribute,
.hljs-literal,
.hljs-meta,
.hljs-number,
.hljs-operator,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-id,
.hljs-variable {
color: #79c0ff;
}
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #a5d6ff;
}
.hljs-built_in,
.hljs-symbol {
color: #ffa657;
}
.hljs-code,
.hljs-comment,
.hljs-formula {
color: #8b949e;
}
.hljs-name,
.hljs-quote,
.hljs-selector-pseudo,
.hljs-selector-tag {
color: #7ee787;
}
.hljs-subst {
color: #c9d1d9;
}
.hljs-section {
color: #1f6feb;
font-weight: 700;
}
.hljs-bullet {
color: #f2cc60;
}
.hljs-emphasis {
color: #c9d1d9;
font-style: italic;
}
.hljs-strong {
color: #c9d1d9;
font-weight: 700;
}
.hljs-addition {
color: #aff5b4;
background-color: #033a16;
}
.hljs-deletion {
color: #ffdcd7;
background-color: #67060c;
}
@@ -0,0 +1,94 @@
pre code.hljs {
display: block;
overflow-x: auto;
padding: 1em;
}
code.hljs {
padding: 3px 5px;
} /*!
Theme: GitHub
Description: Light theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-light
Current colors taken from GitHub's CSS
*/
.hljs {
color: #24292e;
// background: #fff;
}
.hljs-doctag,
.hljs-keyword,
.hljs-meta .hljs-keyword,
.hljs-template-tag,
.hljs-template-variable,
.hljs-type,
.hljs-variable.language_ {
color: #d73a49;
}
.hljs-title,
.hljs-title.class_,
.hljs-title.class_.inherited__,
.hljs-title.function_ {
color: #6f42c1;
}
.hljs-attr,
.hljs-attribute,
.hljs-literal,
.hljs-meta,
.hljs-number,
.hljs-operator,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-id,
.hljs-variable {
color: #005cc5;
}
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #032f62;
}
.hljs-built_in,
.hljs-symbol {
color: #e36209;
}
.hljs-code,
.hljs-comment,
.hljs-formula {
color: #6a737d;
}
.hljs-name,
.hljs-quote,
.hljs-selector-pseudo,
.hljs-selector-tag {
color: #22863a;
}
.hljs-subst {
color: #24292e;
}
.hljs-section {
color: #005cc5;
font-weight: 700;
}
.hljs-bullet {
color: #735c0f;
}
.hljs-emphasis {
color: #24292e;
font-style: italic;
}
.hljs-strong {
color: #24292e;
font-weight: 700;
}
.hljs-addition {
color: #22863a;
background-color: #f0fff4;
}
.hljs-deletion {
color: #b31d28;
background-color: #ffeef0;
}
@@ -0,0 +1,59 @@
//
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import hljs from 'highlight.js'
import { hljsDefineSvelte } from './languages/svelte-hljs'
hljs.registerLanguage('svelte', hljsDefineSvelte)
export interface HighlightOptions {
language: string
}
export function highlightText (text: string, options: HighlightOptions): string {
// We should always use highlighter because it sanitizes the input
// We have to always use highlighter to ensure that the input is sanitized
const validLanguage = options.language !== '' && hljs.getLanguage(options.language) !== undefined
const language = validLanguage ? options.language : 'text'
const { value: highlighted } = hljs.highlight(text, { language })
const normalized = normalizeHighlightTags(highlighted)
return normalized
}
export function highlightLines (lines: string[], options: HighlightOptions): string[] {
const highlighted = highlightText(lines.join('\n'), options)
return highlighted.split('\n')
}
function normalizeHighlightTags (highlighted: string): string {
const openTags: string[] = []
const normalized = highlighted.replace(/(<span[^>]*>)|(<\/span>)|(\n)/g, (match) => {
if (match === '\n') {
return '</span>'.repeat(openTags.length) + '\n' + openTags.join('')
}
if (match === '</span>') {
openTags.pop()
} else {
openTags.push(match)
}
return match
})
return normalized
}
@@ -0,0 +1,17 @@
//
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
export * from './highlight'
export * from './render'
@@ -0,0 +1,48 @@
export function hljsDefineSvelte (hljs: any): any {
return {
subLanguage: 'xml',
contains: [
hljs.COMMENT('<!--', '-->', {
relevance: 10
}),
{
begin: /^(\s*)(<script(\s*context="module")?>)/gm,
end: /^(\s*)(<\/script>)/gm,
subLanguage: 'javascript',
excludeBegin: true,
excludeEnd: true,
contains: [
{
begin: /^(\s*)(\$:)/gm,
end: /(\s*)/gm,
className: 'keyword'
}
]
},
{
begin: /^(\s*)(<style.*>)/gm,
end: /^(\s*)(<\/style>)/gm,
subLanguage: 'css',
excludeBegin: true,
excludeEnd: true
},
{
begin: /\{/gm,
end: /\}/gm,
subLanguage: 'javascript',
contains: [
{
begin: /[{]/,
end: /[}]/,
skip: true
},
{
begin: /([#:/@])(if|else|each|await|then|catch|debug|html)/gm,
className: 'keyword',
relevance: 10
}
]
}
]
}
}
@@ -0,0 +1,86 @@
//
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type DiffHunk, type DiffLine, DiffLineType, EmptyLine } from '@hcengineering/diffview'
import { type HighlightOptions, highlightLines } from './highlight'
export interface RenderOptions {
syntaxHighlight: {
language: string
}
}
export interface DiffLineRenderResult {
before: DiffLine
after: DiffLine
}
export interface DiffHunkRenderResult {
hunk: DiffHunk
lines: DiffLineRenderResult[]
}
export function renderHunk (hunk: DiffHunk, options: RenderOptions): DiffHunkRenderResult {
const syntaxHighlight = options.syntaxHighlight
const highlightOptions: HighlightOptions = { language: syntaxHighlight.language }
const { before, after } = splitDiffLines(hunk.lines)
const beforeHighlighted = highlightDiffLines(before, highlightOptions)
const afterHighlighted = highlightDiffLines(after, highlightOptions)
const lines: DiffLineRenderResult[] = []
while (beforeHighlighted.length > 0 || afterHighlighted.length > 0) {
const beforeLine = beforeHighlighted.shift() ?? EmptyLine
const afterLine = afterHighlighted.shift() ?? EmptyLine
lines.push({ before: beforeLine, after: afterLine })
}
return { hunk, lines }
}
function splitDiffLines (lines: DiffLine[]): { before: DiffLine[], after: DiffLine[] } {
const before: DiffLine[] = []
const after: DiffLine[] = []
for (const line of lines) {
if (line.type === DiffLineType.CONTEXT) {
before.push(line)
after.push(line)
} else if (line.type === DiffLineType.DELETE) {
before.push(line)
after.push(EmptyLine)
} else if (line.type === DiffLineType.INSERT) {
before.push(EmptyLine)
after.push(line)
} else {
before.push(line)
after.push(line)
}
}
return { before, after }
}
function highlightDiffLines (lines: DiffLine[], options: HighlightOptions): DiffLine[] {
// Highlight entire diff hunk content, it is more accurate than highlighting line-by-line
const content = lines.filter((line) => line.type !== DiffLineType.EMPTY).map((line) => line.content)
const highlighted = highlightLines(content, options)
// Reconstruct DiffLine items with highlighted content
return lines.map((line) =>
line.type === DiffLineType.EMPTY ? { ...line } : { ...line, content: highlighted.shift() ?? '' }
)
}
+24
View File
@@ -0,0 +1,24 @@
//
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type Resources } from '@hcengineering/platform'
import DiffView from './components/DiffView.svelte'
import InlineDiffView from './components/InlineDiffView.svelte'
export default async (): Promise<Resources> => ({
component: {
DiffView,
InlineDiffView
}
})
+103
View File
@@ -0,0 +1,103 @@
//
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type Diff2HtmlConfig, parse } from 'diff2html'
import {
type DiffBlock as D2HDiffBlock,
type DiffFile as D2HDiffFile,
type DiffLine as D2HDiffLine
} from 'diff2html/lib/types'
import {
type Diff,
type DiffFile,
type DiffFileType,
type DiffHunk,
type DiffLine,
DiffLineType
} from '@hcengineering/diffview'
import { isDevNullName } from './utils'
const diff2htmlConfig: Diff2HtmlConfig = {
diffMaxChanges: 10000
}
/**
* @public
*/
export function parseDiff (diff: Diff): DiffFile[] {
const files = parse(diff, diff2htmlConfig)
return files.map(mapFile)
}
function mapFile (file: D2HDiffFile): DiffFile {
const { language, oldName, newName, addedLines, deletedLines, isBinary, isTooBig } = file
const fileName = isDevNullName(newName) ? oldName : newName
const sha = file.checksumAfter ?? ''
const diffType = mapFileDiffType(file)
const stats = { addedLines, deletedLines }
const hunks = isTooBig === true ? [] : file.blocks.map(mapHunk)
return { hunks, language, oldName, newName, fileName, sha, isBinary, isTooBig, diffType, stats }
}
function mapHunk (block: D2HDiffBlock): DiffHunk {
const { header, oldStartLine, newStartLine } = block
const lines = block.lines.map(mapLine)
return { header, oldStartLine, newStartLine, lines }
}
function mapLine (line: D2HDiffLine): DiffLine {
const { type, oldNumber, newNumber } = line
const { prefix, content } = parseContentLine(line.content)
switch (type) {
case 'context':
return { type: DiffLineType.CONTEXT, oldNumber, newNumber, prefix, content }
case 'insert':
return { type: DiffLineType.INSERT, oldNumber: undefined, newNumber, prefix, content }
case 'delete':
return { type: DiffLineType.DELETE, oldNumber, newNumber: undefined, prefix, content }
default:
throw new Error(`Unexpected line type: ${type}`)
}
}
function mapFileDiffType (file: D2HDiffFile): DiffFileType {
if (file.isNew === true) {
return 'add'
}
if (file.isDeleted === true) {
return 'delete'
}
if (file.isRename === true) {
return 'rename'
}
if (file.isCopy === true) {
return 'copy'
}
return 'modify'
}
const prefixLength = 1
function parseContentLine (line: string): { prefix: string, content: string } {
return {
prefix: line.substring(0, prefixLength),
content: line.substring(prefixLength)
}
}
+28
View File
@@ -0,0 +1,28 @@
//
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type IntlString, mergeIds } from '@hcengineering/platform'
import diffview, { diffviewId } from '@hcengineering/diffview'
export default mergeIds(diffviewId, diffview, {
string: {
ShowDiff: '' as IntlString,
LargeDiffsAreHidden: '' as IntlString,
NoChanges: '' as IntlString,
FileIsTooLarge: '' as IntlString,
FileWasRenamed: '' as IntlString,
FileWasDeleted: '' as IntlString
}
})
+37
View File
@@ -0,0 +1,37 @@
//
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type DiffFile } from '@hcengineering/diffview'
export function isDevNullName (name: string): boolean {
return name === '/dev/null'
}
export function getFileName (file: DiffFile): string {
const { oldName, newName } = file
return isDevNullName(newName) ? oldName : newName
}
export function formatFileName (file: DiffFile): string {
const { oldName, newName } = file
if (oldName !== newName && !isDevNullName(oldName) && !isDevNullName(newName)) {
return oldName + ' → ' + newName
} else if (!isDevNullName(newName)) {
return newName
} else {
return oldName
}
}