mirror of https://github.com/Squidex/squidex.git
Browse Source
* Refactorings * T * Move to labels * Comments. * Fix invalidation. * Test * Tests * Fix e2epull/1299/head
committed by
GitHub
164 changed files with 2616 additions and 3120 deletions
File diff suppressed because one or more lines are too long
@ -1,48 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
const CopyPlugin = require('copy-webpack-plugin'); |
|||
|
|||
class FilterSassWarningsPlugin { |
|||
apply(compiler) { |
|||
compiler.hooks.done.tap('FilterSassWarningsPlugin', (stats) => { |
|||
stats.compilation.warnings = stats.compilation.warnings.filter(warning => { |
|||
const message = warning.message || warning.toString(); |
|||
return !message.includes('sass-loader'); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"], |
|||
addons: [ |
|||
"@storybook/addon-links", |
|||
"@storybook/addon-interactions" |
|||
], |
|||
framework: { |
|||
name: "@storybook/angular", |
|||
options: {} |
|||
}, |
|||
webpackFinal: async config => { |
|||
/* |
|||
* Copy lazy loaded libraries to output. |
|||
*/ |
|||
config.plugins.push(new CopyPlugin({ |
|||
patterns: [ |
|||
{ from: './node_modules/ace-builds/src-min/', to: './dependencies/ace/' }, |
|||
] |
|||
})); |
|||
|
|||
config.plugins.push(new FilterSassWarningsPlugin()); |
|||
config.resolve?.extensions?.push('.d.ts'); |
|||
return config; |
|||
}, |
|||
docs: { |
|||
autodocs: true |
|||
} |
|||
}; |
|||
@ -0,0 +1,94 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import type { StorybookConfig } from '@analogjs/storybook-angular'; |
|||
import { viteStaticCopy } from 'vite-plugin-static-copy'; |
|||
import tsconfigPaths from 'vite-tsconfig-paths'; |
|||
import { fileURLToPath } from 'url'; |
|||
import { dirname, resolve } from 'path'; |
|||
|
|||
const __filename = fileURLToPath(import.meta.url); |
|||
const __dirname = dirname(__filename); |
|||
|
|||
const config: StorybookConfig = { |
|||
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], |
|||
addons: ['@storybook/addon-links'], |
|||
framework: { |
|||
name: '@analogjs/storybook-angular', |
|||
options: {}, |
|||
}, |
|||
async viteFinal(config) { |
|||
const { mergeConfig } = await import('vite'); |
|||
|
|||
return mergeConfig(config, { |
|||
plugins: [ |
|||
tsconfigPaths(), |
|||
viteStaticCopy({ |
|||
targets: [ |
|||
{ |
|||
src: './node_modules/ace-builds/src-min/*', |
|||
dest: 'dependencies/ace', |
|||
}, |
|||
], |
|||
}), |
|||
], |
|||
resolve: { |
|||
extensions: [ |
|||
'.ts', |
|||
'.tsx', |
|||
'.mjs', |
|||
'.js', |
|||
'.jsx', |
|||
'.json', |
|||
'.d.ts', |
|||
], |
|||
alias: { |
|||
'@app/framework/internal': resolve( |
|||
__dirname, |
|||
'../src/app/framework/internal.ts', |
|||
), |
|||
'@app/framework': resolve( |
|||
__dirname, |
|||
'../src/app/framework/index.ts', |
|||
), |
|||
'@app/shared/internal': resolve( |
|||
__dirname, |
|||
'../src/app/shared/internal.ts', |
|||
), |
|||
'@app/shared': resolve( |
|||
__dirname, |
|||
'../src/app/shared/index.ts', |
|||
), |
|||
}, |
|||
}, |
|||
css: { |
|||
...config.css, |
|||
preprocessorOptions: { |
|||
scss: { |
|||
loadPaths: [resolve(__dirname, '../src/app/theme')], |
|||
includePaths: ['node_modules'], |
|||
quietDeps: true, |
|||
silenceDeprecations: ['color-functions', 'global-builtin', 'import', 'if-function'], |
|||
}, |
|||
}, |
|||
}, |
|||
optimizeDeps: { |
|||
exclude: ['function-bind'], |
|||
}, |
|||
ssr: { |
|||
noExternal: [ |
|||
'@angular/core', |
|||
'@angular/common', |
|||
'@angular/forms', |
|||
'@angular/router', |
|||
], |
|||
}, |
|||
}); |
|||
}, |
|||
}; |
|||
|
|||
export default config; |
|||
@ -1,21 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
export const parameters = { |
|||
actions: { |
|||
argTypesRegex: "^on[A-Z].*" |
|||
}, |
|||
controls: { |
|||
matchers: { |
|||
color: /(background|color)$/i, |
|||
date: /Date$/, |
|||
}, |
|||
}, |
|||
docs: { |
|||
inlineStories: true |
|||
}, |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Preview } from "@storybook/angular"; |
|||
import "./../src/styles.scss"; |
|||
|
|||
const preview: Preview = { |
|||
parameters: { |
|||
actions: { |
|||
argTypesRegex: "^on[A-Z].*", |
|||
}, |
|||
controls: { |
|||
matchers: { |
|||
color: /(background|color)$/i, |
|||
date: /Date$/, |
|||
}, |
|||
}, |
|||
docs: { |
|||
inlineStories: true, |
|||
}, |
|||
}, |
|||
}; |
|||
|
|||
export default preview; |
|||
File diff suppressed because it is too large
@ -1,35 +1,152 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
const fs = require("fs"); |
|||
const path = require("path"); |
|||
|
|||
class AddButtonTypesStrategy { |
|||
#buttonTagPattern = /<button(\s[^>]*?)?>/gi; |
|||
|
|||
fix(html) { |
|||
return html.replace(this.#buttonTagPattern, (match, attributes) => { |
|||
if (attributes && /\btype\s*=/i.test(attributes)) { |
|||
return match; |
|||
} |
|||
return attributes ? `<button type="button"${attributes}>` : `<button type="button">`; |
|||
}); |
|||
} |
|||
} |
|||
|
|||
class FixIconOnlyInteractivesStrategy { |
|||
#interactiveTagPattern = /<(a|button)(\s[^>]*)?>/gi; |
|||
#visuallyHiddenSpanPattern = /\s*<span\s+class="visually-hidden">([\s\S]*?)<\/span>/i; |
|||
#nestedInteractiveOpenPattern = /<(a|button)[\s>]/gi; |
|||
#nestedInteractiveClosePattern = /<\/(a|button)\s*>/gi; |
|||
|
|||
#findClosingTag(html, from, tag) { |
|||
const openingTagPattern = new RegExp(`<${tag}[\\s>]`, "gi"); |
|||
const closingTagPattern = new RegExp(`<\\/${tag}\\s*>`, "gi"); |
|||
let depth = 1; |
|||
let position = from; |
|||
|
|||
while (depth > 0) { |
|||
openingTagPattern.lastIndex = position; |
|||
closingTagPattern.lastIndex = position; |
|||
const nextOpening = openingTagPattern.exec(html); |
|||
const nextClosing = closingTagPattern.exec(html); |
|||
|
|||
if (!nextClosing) { |
|||
return html.length; |
|||
} |
|||
|
|||
if (nextOpening && nextOpening.index < nextClosing.index) { |
|||
depth++; |
|||
position = nextOpening.index + 1; |
|||
} else { |
|||
depth--; |
|||
position = nextClosing.index + nextClosing[0].length; |
|||
} |
|||
} |
|||
return position; |
|||
} |
|||
|
|||
#isDirectChild(inner, spanIndex) { |
|||
let depth = 0; |
|||
let position = 0; |
|||
|
|||
while (position < spanIndex) { |
|||
this.#nestedInteractiveOpenPattern.lastIndex = position; |
|||
this.#nestedInteractiveClosePattern.lastIndex = position; |
|||
const nextOpening = this.#nestedInteractiveOpenPattern.exec(inner); |
|||
const nextClosing = this.#nestedInteractiveClosePattern.exec(inner); |
|||
|
|||
const openingBeforeSpan = nextOpening && nextOpening.index < spanIndex; |
|||
const closingBeforeSpan = nextClosing && nextClosing.index < spanIndex; |
|||
|
|||
if (openingBeforeSpan && (!closingBeforeSpan || nextOpening.index < nextClosing.index)) { |
|||
depth++; |
|||
position = nextOpening.index + 1; |
|||
} else if (closingBeforeSpan) { |
|||
depth--; |
|||
position = nextClosing.index + 1; |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return depth === 0; |
|||
} |
|||
|
|||
fix(html) { |
|||
const fixes = []; |
|||
let match; |
|||
|
|||
this.#interactiveTagPattern.lastIndex = 0; |
|||
|
|||
while ((match = this.#interactiveTagPattern.exec(html)) !== null) { |
|||
const tag = match[1]; |
|||
const attributes = match[2] || ""; |
|||
const openEnd = match.index + match[0].length; |
|||
const closeEnd = this.#findClosingTag(html, openEnd, tag); |
|||
const inner = html.slice(openEnd, closeEnd - `</${tag}>`.length); |
|||
const spanMatch = this.#visuallyHiddenSpanPattern.exec(inner); |
|||
|
|||
if (!spanMatch || !this.#isDirectChild(inner, spanMatch.index)) { |
|||
continue; |
|||
} |
|||
|
|||
const ariaLabel = spanMatch[1].trim().replace(/"/g, "'"); |
|||
const innerWithout = inner.slice(0, spanMatch.index) + inner.slice(spanMatch.index + spanMatch[0].length); |
|||
const newOpenTag = `<${tag}${attributes} attr.aria-label="${ariaLabel}">`; |
|||
|
|||
fixes.push({ |
|||
from: match.index, |
|||
to: openEnd + inner.length, |
|||
newOpenTag, |
|||
newInner: innerWithout, |
|||
}); |
|||
} |
|||
|
|||
let result = html; |
|||
for (const fix of fixes.sort((a, b) => b.from - a.from)) { |
|||
result = result.slice(0, fix.from) + fix.newOpenTag + fix.newInner + result.slice(fix.to); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
|
|||
const ROOT_DIR = "."; |
|||
const SKIP_DIRS = ["node_modules", ".git", "dist", "build", ".cache"]; |
|||
const BUTTON_TAG_RE = /<button(\s[^>]*?)?>/gi; |
|||
const SKIP_FILES = ["_theme.html"]; |
|||
|
|||
function fixButtons(html) { |
|||
return html.replace(BUTTON_TAG_RE, (match, attrs) => { |
|||
if (attrs && /\btype\s*=/i.test(attrs)) return match; |
|||
return attrs ? `<button type="button"${attrs}>` : `<button type="button">`; |
|||
}); |
|||
} |
|||
const strategies = [ |
|||
new AddButtonTypesStrategy(), |
|||
new FixIconOnlyInteractivesStrategy(), |
|||
]; |
|||
|
|||
function walk(dir) { |
|||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { |
|||
const fullPath = path.join(dir, entry.name); |
|||
|
|||
if (entry.isDirectory()) { |
|||
if (!SKIP_DIRS.includes(entry.name)) walk(fullPath); |
|||
} else if (entry.isFile() && entry.name.endsWith(".component.html")) { |
|||
const original = fs.readFileSync(fullPath, "utf8"); |
|||
const fixed = fixButtons(original); |
|||
|
|||
if (fixed !== original) { |
|||
fs.writeFileSync(fullPath, fixed, "utf8"); |
|||
console.log(`Fixed: ${fullPath}`); |
|||
} |
|||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { |
|||
const fullPath = path.join(dir, entry.name); |
|||
|
|||
if (entry.isDirectory()) { |
|||
if (!SKIP_DIRS.includes(entry.name)) { |
|||
walk(fullPath); |
|||
} |
|||
} else if (entry.isFile() && entry.name.endsWith(".component.html") && !SKIP_FILES.includes(entry.name)) { |
|||
let content = fs.readFileSync(fullPath, "utf8"); |
|||
let changed = false; |
|||
|
|||
for (const strategy of strategies) { |
|||
const updated = strategy.fix(content, fullPath); |
|||
if (updated !== content) { |
|||
content = updated; |
|||
changed = true; |
|||
} |
|||
} |
|||
|
|||
if (changed) { |
|||
fs.writeFileSync(fullPath, content, "utf8"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
walk(path.resolve(ROOT_DIR)); |
|||
@ -1,3 +1,25 @@ |
|||
<sqx-layout layout="right" titleText="i18n:comments.title" white="true" width="20"> |
|||
<sqx-comments [commentsId]="commentsId | async" /> |
|||
</sqx-layout> |
|||
@if (commentsTab | async; as tab) { |
|||
<sqx-layout layout="right" white="true" width="20"> |
|||
<ng-container menu> |
|||
<ul class="nav nav-tabs2"> |
|||
<li class="nav-item"> |
|||
<a |
|||
class="nav-link" |
|||
[class.active]="tab === 'unresolved'" |
|||
[queryParams]="{ commentsTab: 'unresolved' }" |
|||
queryParamsHandling="merge" |
|||
[routerLink]="[]"> |
|||
{{ "comments.unresolved" | sqxTranslate }} |
|||
</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link" [class.active]="tab === 'all'" [queryParams]="{ commentsTab: 'all' }" queryParamsHandling="merge" [routerLink]="[]"> |
|||
{{ "comments.all" | sqxTranslate }} |
|||
</a> |
|||
</li> |
|||
</ul> |
|||
</ng-container> |
|||
|
|||
<sqx-comments [content]="content | async" [commentsId]="commentsId | async" [resolved]="tab === 'all'" /> |
|||
</sqx-layout> |
|||
} |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue