mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da3922586a | ||
|
|
28184f8d4d | ||
|
|
a4e7522ca0 | ||
|
|
59b3d30323 | ||
|
|
53cac0b614 | ||
|
|
82c29398d3 | ||
|
|
6d289e31c7 | ||
|
|
5ec02c3089 | ||
|
|
9940273cd9 | ||
|
|
21c7b9872e | ||
|
|
fa99ec6f8f | ||
|
|
78f7db9250 | ||
|
|
c101610c46 |
No files matched your search
@@ -1,15 +1,3 @@
|
||||
> [!CAUTION]
|
||||
> As of this writing (15 June 2026), we are working to finalise the API and self-hosting documentation over the next few days.
|
||||
>
|
||||
> We apologise for the brief delay in open-source releases. We paused after spam waves created safety concerns while we built out Fluxer's trust and safety infrastructure. During that same stretch, we have been fixing hundreds of bugs, adding new features, and preparing a much improved audio and video system.
|
||||
>
|
||||
> You can already try that work in the Fluxer Canary client: [download Canary](https://canary.fluxer.app/download) or [open Canary on the web](https://web.canary.fluxer.app). The latest stable client remains out of date for now, but over the coming weeks we are finalising the remaining work needed to stabilise the current latest code out in the open.
|
||||
|
||||
> [!NOTE]
|
||||
> Learn about the developer behind Fluxer, the goals of the project, the tech stack, and what's coming next.
|
||||
>
|
||||
> [Read the launch blog post](https://blog.fluxer.app/how-i-built-fluxer-a-discord-like-chat-app/) | [View full roadmap](https://blog.fluxer.app/roadmap-2026/)
|
||||
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./fluxer_static/marketing/branding/logo-white.svg">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
.container {
|
||||
overflow: hidden;
|
||||
background-color: var(--background-primary);
|
||||
z-index: 2;
|
||||
z-index: var(--z-index-popout);
|
||||
}
|
||||
|
||||
.containerAttached {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import UnicodeEmojis from '@app/features/expressions/utils/UnicodeEmojis';
|
||||
import {
|
||||
$captureSelectionOffsets,
|
||||
$getComposerNodeDisplayStart,
|
||||
@@ -12,7 +13,7 @@ import {
|
||||
$isComposerStandardEmojiNode,
|
||||
} from '@app/features/lexical/composer/nodes/ComposerStandardEmojiNode';
|
||||
import {$isSyntaxMarkerNode} from '@app/features/lexical/composer/nodes/SyntaxMarkerNode';
|
||||
import {findTypedEmojiShortcode} from '@app/features/messaging/utils/markdown/TypedEmojiMatch';
|
||||
import {findTypedEmojiShortcode, type TypedEmojiMatch} from '@app/features/messaging/utils/markdown/TypedEmojiMatch';
|
||||
import type {ResolvedTypedEmoji} from '@app/features/messaging/utils/TypedEmojiShortcodeUtils';
|
||||
import {type LexicalEditor, TextNode} from 'lexical';
|
||||
|
||||
@@ -26,6 +27,46 @@ export function registerComposerEmojiShortcode(editor: LexicalEditor, resolve: C
|
||||
});
|
||||
}
|
||||
|
||||
function findUnicodeEmoji(text: string, startIndex: number): TypedEmojiMatch | null {
|
||||
const pattern = new RegExp(UnicodeEmojis.EMOJI_SURROGATE_RE.source, 'g');
|
||||
pattern.lastIndex = startIndex;
|
||||
const match = pattern.exec(text);
|
||||
if (match == null) {
|
||||
return null;
|
||||
}
|
||||
const name = UnicodeEmojis.nameForSurrogate(match[0], false);
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
return {start: match.index, end: match.index + match[0].length, name};
|
||||
}
|
||||
|
||||
interface EmojiToken extends TypedEmojiMatch {
|
||||
surrogate: boolean;
|
||||
}
|
||||
|
||||
function findNextEmojiToken(text: string, startIndex: number): EmojiToken | null {
|
||||
const shortcode = findTypedEmojiShortcode(text, startIndex);
|
||||
const surrogate = findUnicodeEmoji(text, startIndex);
|
||||
if (shortcode == null) {
|
||||
return surrogate == null ? null : {...surrogate, surrogate: true};
|
||||
}
|
||||
if (surrogate == null) {
|
||||
return {...shortcode, surrogate: false};
|
||||
}
|
||||
return surrogate.start < shortcode.start ? {...surrogate, surrogate: true} : {...shortcode, surrogate: false};
|
||||
}
|
||||
|
||||
function isEscapedAt(text: string, index: number): boolean {
|
||||
let backslashes = 0;
|
||||
let cursor = index - 1;
|
||||
while (cursor >= 0 && text[cursor] === '\\') {
|
||||
backslashes += 1;
|
||||
cursor -= 1;
|
||||
}
|
||||
return backslashes % 2 === 1;
|
||||
}
|
||||
|
||||
export function $convertEmojiShortcode(node: TextNode, resolve: ComposerEmojiResolver): void {
|
||||
if ($isSyntaxMarkerNode(node) || node.hasFormat('code')) {
|
||||
return;
|
||||
@@ -37,7 +78,7 @@ export function $convertEmojiShortcode(node: TextNode, resolve: ComposerEmojiRes
|
||||
const text = node.getTextContent();
|
||||
let searchFrom = 0;
|
||||
while (true) {
|
||||
const match = findTypedEmojiShortcode(text, searchFrom);
|
||||
const match = findNextEmojiToken(text, searchFrom);
|
||||
if (match == null) {
|
||||
return;
|
||||
}
|
||||
@@ -45,6 +86,22 @@ export function $convertEmojiShortcode(node: TextNode, resolve: ComposerEmojiRes
|
||||
if ((codeMarkers == null ? 0 : codeMarkers.length) % 2 === 1) {
|
||||
return;
|
||||
}
|
||||
if (isEscapedAt(text, match.start)) {
|
||||
if (match.surrogate) {
|
||||
const shortcodeText = `:${match.name}:`;
|
||||
const selection = $captureSelectionOffsets();
|
||||
const nodeDisplayStart = $getComposerNodeDisplayStart(node);
|
||||
const replaced = `${text.slice(0, match.start)}${shortcodeText}${text.slice(match.end)}`;
|
||||
node.setTextContent(replaced);
|
||||
if (selection != null) {
|
||||
const adjusted = $adjustEmbedCaret(selection, nodeDisplayStart, match.start, match.end, shortcodeText.length);
|
||||
$selectComposerRange(adjusted.anchor, adjusted.focus);
|
||||
}
|
||||
return;
|
||||
}
|
||||
searchFrom = match.end;
|
||||
continue;
|
||||
}
|
||||
const previous = match.start === 0 ? node.getPreviousSibling() : null;
|
||||
if (/^skin-tone-[1-5]$/.test(match.name) && $isComposerStandardEmojiNode(previous)) {
|
||||
const baseName = previous.getEmojiName().split('::')[0]!;
|
||||
|
||||
@@ -139,6 +139,16 @@ function $reconcileParagraph(paragraph: ParagraphNode, parserFlags?: number): vo
|
||||
}
|
||||
}
|
||||
|
||||
function $isEscapedAtOffset(source: string, offset: number): boolean {
|
||||
let backslashes = 0;
|
||||
let cursor = offset - 1;
|
||||
while (cursor >= 0 && source[cursor] === '\\') {
|
||||
backslashes += 1;
|
||||
cursor -= 1;
|
||||
}
|
||||
return backslashes % 2 === 1;
|
||||
}
|
||||
|
||||
function $reconcileLine(
|
||||
line: Array<LexicalNode>,
|
||||
parserFlags?: number,
|
||||
@@ -153,13 +163,15 @@ function $reconcileLine(
|
||||
const nodeText = $nodeWireText(node);
|
||||
const nodeEnd = sourceOffset + nodeText.length;
|
||||
if ($isComposerMentionNode(node) || $isComposerCustomEmojiNode(node) || $isComposerStandardEmojiNode(node)) {
|
||||
const literal = spans.some(
|
||||
(span) =>
|
||||
span.role === 'content' &&
|
||||
(span.format & MarkdownHl.code) !== 0 &&
|
||||
span.start <= sourceOffset &&
|
||||
span.end >= nodeEnd,
|
||||
);
|
||||
const literal =
|
||||
$isEscapedAtOffset(source, sourceOffset) ||
|
||||
spans.some(
|
||||
(span) =>
|
||||
span.role === 'content' &&
|
||||
(span.format & MarkdownHl.code) !== 0 &&
|
||||
span.start <= sourceOffset &&
|
||||
span.end >= nodeEnd,
|
||||
);
|
||||
if (node.isLiteral() !== literal) {
|
||||
node.setLiteral(literal);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "24.10.9",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260130.1",
|
||||
"electron": "43.4.0",
|
||||
"electron": "42.10.0",
|
||||
"electron-builder": "26.9.0",
|
||||
"esbuild": "0.27.2",
|
||||
"typescript": "5.9.3"
|
||||
|
||||
+1
-1
Submodule fluxer_marketing updated: 1915e599ac...530c44ec38.
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: ar\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: bg\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: cs\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: da\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: el\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: en-GB\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: en-US\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: es-419\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: es-ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: fi\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: he\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: hi\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: hr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: hu\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: id\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: ja\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: ko\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: lt\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: nl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: no\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: pt-BR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: ro\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: sv-SE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: th\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: tr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: uk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: vi\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: zh-CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fluxer-marketing\n"
|
||||
"POT-Creation-Date: 2026-08-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-08-25 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
||||
"Language: zh-TW\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
@@ -106,6 +106,25 @@ fn parse_inline_with_context(
|
||||
position += 2;
|
||||
continue;
|
||||
}
|
||||
if next == b':'
|
||||
&& let Some(result) = links::parse_emoji_shortcode(parser, &text[position + 1..])
|
||||
&& let Node::Emoji {
|
||||
kind: EmojiKind::Standard { raw, .. },
|
||||
} = &result.node
|
||||
{
|
||||
accumulated.extend_from_slice(raw.as_bytes());
|
||||
position += 1 + result.advance;
|
||||
continue;
|
||||
}
|
||||
if let Some(emoji) = parser
|
||||
.emoji_context()
|
||||
.standard_at(base_offset + position + 1)
|
||||
&& text[position + 1..].starts_with(&emoji.raw)
|
||||
{
|
||||
accumulated.extend_from_slice(emoji.raw.as_bytes());
|
||||
position += 1 + emoji.len;
|
||||
continue;
|
||||
}
|
||||
if is_escapable_character(next)
|
||||
|| is_ordered_list_marker_dot_escape(text, position)
|
||||
|| is_word_dot_escape(text, position)
|
||||
|
||||
Generated
+5
-5
@@ -1680,8 +1680,8 @@ importers:
|
||||
specifier: 7.0.0-dev.20260130.1
|
||||
version: 7.0.0-dev.20260130.1
|
||||
electron:
|
||||
specifier: 43.4.0
|
||||
version: 43.4.0
|
||||
specifier: 42.10.0
|
||||
version: 42.10.0
|
||||
electron-builder:
|
||||
specifier: 26.9.0
|
||||
version: 26.9.0([email protected])
|
||||
@@ -8222,8 +8222,8 @@ packages:
|
||||
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
electron@43.4.0:
|
||||
resolution: {integrity: sha512-3qxGF0CeQbiox5oWV1JlbWGQ1VerbmDhTFqW4sJ8h7uqTHniFYPObXJcDna0DMh32et0fFyKzz0YY8lJv3t5jg==}
|
||||
electron@42.10.0:
|
||||
resolution: {integrity: sha512-yBWnZO8tKId0ADvGa4+imPmm3+8Kfs8MtkUPhQQJz1AjTnUUDwCTWkQEU0CrArVVL97Jo4Q/0mVcLHciicBDGQ==}
|
||||
engines: {node: '>= 22.12.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -18314,7 +18314,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
electron@43.4.0:
|
||||
electron@42.10.0:
|
||||
dependencies:
|
||||
'@electron-internal/extract-zip': 1.0.5
|
||||
'@electron/get': 5.1.0
|
||||
|
||||
+14
-22
@@ -78,13 +78,20 @@ fn wasm_clang_candidates() -> Vec<PathBuf> {
|
||||
}
|
||||
|
||||
fn discover_wasm_clang_from(candidates: Vec<PathBuf>) -> Result<PathBuf> {
|
||||
discover_wasm_clang_with(candidates, clang_targets_wasm32)
|
||||
}
|
||||
|
||||
fn discover_wasm_clang_with(
|
||||
candidates: Vec<PathBuf>,
|
||||
targets_wasm32: impl Fn(&Path) -> bool,
|
||||
) -> Result<PathBuf> {
|
||||
let mut seen: Vec<PathBuf> = Vec::new();
|
||||
for candidate in candidates {
|
||||
if seen.contains(&candidate) {
|
||||
continue;
|
||||
}
|
||||
seen.push(candidate.clone());
|
||||
if clang_targets_wasm32(&candidate) {
|
||||
if targets_wasm32(&candidate) {
|
||||
return Ok(candidate);
|
||||
}
|
||||
}
|
||||
@@ -495,19 +502,13 @@ export const MARKDOWN_PARSER_WASM_BASE64 =\n\
|
||||
|
||||
#[test]
|
||||
fn wasm_clang_discovery_prefers_a_wasm32_capable_candidate_over_an_earlier_one() {
|
||||
let temp = TempDir::new().expect("temp dir");
|
||||
let clang = temp.path().join("clang");
|
||||
fs::write(
|
||||
&clang,
|
||||
"#!/bin/sh\nif [ \"$1\" = \"--print-targets\" ]; then echo ' wasm32 - WebAssembly 32-bit'; fi\n",
|
||||
let capable = PathBuf::from("/opt/llvm/bin/clang");
|
||||
let found = discover_wasm_clang_with(
|
||||
vec![PathBuf::from("/nonexistent/clang"), capable.clone()],
|
||||
|candidate| candidate == capable,
|
||||
)
|
||||
.expect("write fake clang");
|
||||
make_executable(&clang);
|
||||
|
||||
let found =
|
||||
discover_wasm_clang_from(vec![PathBuf::from("/nonexistent/clang"), clang.clone()])
|
||||
.expect("the fake clang advertises wasm32");
|
||||
assert_eq!(found, clang);
|
||||
.expect("the capable candidate advertises wasm32");
|
||||
assert_eq!(found, capable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -521,15 +522,6 @@ export const MARKDOWN_PARSER_WASM_BASE64 =\n\
|
||||
assert_eq!(discover_wasm_ar(&clang), Some(archiver));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn make_executable(path: &Path) {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
fs::set_permissions(path, fs::Permissions::from_mode(0o755)).expect("chmod");
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn make_executable(_path: &Path) {}
|
||||
|
||||
#[test]
|
||||
fn libfluxcore_bindgen_dts_reset_hook_is_inserted() {
|
||||
let content = "export function is_animated_image(input: Uint8Array): boolean;\nexport type InitInput = RequestInfo;";
|
||||
|
||||
+106
-39
@@ -897,10 +897,32 @@ fn apt_get(args: &[&str]) -> Result<()> {
|
||||
}
|
||||
|
||||
fn install_msvc_arm64_tools_step() -> Result<()> {
|
||||
let installer =
|
||||
Path::new(r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe");
|
||||
let install_path = Path::new(r"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools");
|
||||
run_command(CommandSpec::new(installer).args([
|
||||
let program_files_x86 = env::var_os("ProgramFiles(x86)")
|
||||
.map(PathBuf::from)
|
||||
.ok_or_else(|| anyhow!("ProgramFiles(x86) is not set on the Windows runner"))?;
|
||||
let installer_dir = program_files_x86
|
||||
.join("Microsoft Visual Studio")
|
||||
.join("Installer");
|
||||
let installer = installer_dir.join("setup.exe");
|
||||
let vswhere = installer_dir.join("vswhere.exe");
|
||||
ensure!(
|
||||
vswhere.is_file(),
|
||||
"Visual Studio locator not found: {}",
|
||||
vswhere.display()
|
||||
);
|
||||
|
||||
let install_path = resolve_visual_studio_install_path(&vswhere)?;
|
||||
if let Some(linker) = find_msvc_arm64_linker(&install_path)? {
|
||||
println!("ARM64 cross link.exe: {}", linker.display());
|
||||
return Ok(());
|
||||
}
|
||||
ensure!(
|
||||
installer.is_file(),
|
||||
"Visual Studio installer not found: {}",
|
||||
installer.display()
|
||||
);
|
||||
|
||||
run_command(CommandSpec::new(&installer).args([
|
||||
"modify",
|
||||
"--installPath",
|
||||
install_path.to_string_lossy().as_ref(),
|
||||
@@ -924,30 +946,66 @@ fn install_msvc_arm64_tools_step() -> Result<()> {
|
||||
"VS installer did not finish within the timeout."
|
||||
);
|
||||
|
||||
let mut found = false;
|
||||
let linker = find_msvc_arm64_linker(&install_path)?.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"ARM64 cross-build tools were not installed under {}\\VC\\Tools\\MSVC\\*\\bin\\HostX64\\arm64",
|
||||
install_path.display()
|
||||
)
|
||||
})?;
|
||||
println!("ARM64 cross link.exe: {}", linker.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_visual_studio_install_path(vswhere: &Path) -> Result<PathBuf> {
|
||||
let output = output_text(CommandSpec::new(vswhere).args([
|
||||
"-products",
|
||||
"*",
|
||||
"-latest",
|
||||
"-prerelease",
|
||||
"-property",
|
||||
"installationPath",
|
||||
]))?;
|
||||
let paths = output
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
ensure!(
|
||||
paths.len() == 1,
|
||||
"vswhere returned {} Visual Studio installation paths; expected exactly one",
|
||||
paths.len()
|
||||
);
|
||||
let install_path = PathBuf::from(paths[0]);
|
||||
ensure!(
|
||||
install_path.is_dir(),
|
||||
"vswhere returned a missing Visual Studio installation: {}",
|
||||
install_path.display()
|
||||
);
|
||||
println!("Visual Studio installation: {}", install_path.display());
|
||||
Ok(install_path)
|
||||
}
|
||||
|
||||
fn find_msvc_arm64_linker(install_path: &Path) -> Result<Option<PathBuf>> {
|
||||
let msvc_root = install_path.join("VC").join("Tools").join("MSVC");
|
||||
if msvc_root.exists() {
|
||||
for entry in fs::read_dir(&msvc_root)
|
||||
.with_context(|| format!("Failed to read {}", msvc_root.display()))?
|
||||
{
|
||||
let candidate = entry?
|
||||
.path()
|
||||
.join("bin")
|
||||
.join("HostX64")
|
||||
.join("arm64")
|
||||
.join("link.exe");
|
||||
if candidate.exists() {
|
||||
println!("ARM64 cross link.exe: {}", candidate.display());
|
||||
found = true;
|
||||
}
|
||||
if !msvc_root.is_dir() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut linkers = Vec::new();
|
||||
for entry in fs::read_dir(&msvc_root)
|
||||
.with_context(|| format!("Failed to read {}", msvc_root.display()))?
|
||||
{
|
||||
let candidate = entry?
|
||||
.path()
|
||||
.join("bin")
|
||||
.join("HostX64")
|
||||
.join("arm64")
|
||||
.join("link.exe");
|
||||
if candidate.is_file() {
|
||||
linkers.push(candidate);
|
||||
}
|
||||
}
|
||||
ensure!(
|
||||
found,
|
||||
"ARM64 cross-build tools were not installed under {}\\*\\bin\\HostX64\\arm64.",
|
||||
msvc_root.display()
|
||||
);
|
||||
Ok(())
|
||||
linkers.sort();
|
||||
Ok(linkers.pop())
|
||||
}
|
||||
|
||||
fn windows_installer_process_running() -> Result<bool> {
|
||||
@@ -1823,7 +1881,16 @@ fn create_portable_zip_windows_step() -> Result<()> {
|
||||
}
|
||||
|
||||
const FLUXER_WINDOWS_SIGNER_COMMON_NAME: &str = "Fluxer Platform AB";
|
||||
const THIRD_PARTY_PUBLISHER_ALLOWLIST: &[&str] = &[];
|
||||
const THIRD_PARTY_WINDOWS_SIGNATURE_ALLOWLIST: &[(&str, &str)] = &[
|
||||
(
|
||||
"d3dcompiler_47.dll",
|
||||
"CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
|
||||
),
|
||||
(
|
||||
"dxil.dll",
|
||||
"CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
|
||||
),
|
||||
];
|
||||
const KNOWN_OPTIONAL_WINDOWS_PE_INVENTORY: &[&str] = &["fluxer-vulkan-layer.win32-ia32-msvc.dll"];
|
||||
const WINDOWS_NATIVE_ADDON_STEMS: &[&str] = &[
|
||||
"hardware-encoder",
|
||||
@@ -2144,7 +2211,7 @@ fn assert_fluxer_signed(row: &SignatureRow) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn assert_third_party_signed(row: &SignatureRow) -> Result<()> {
|
||||
fn assert_third_party_signed(row: &SignatureRow, relative: &str) -> Result<()> {
|
||||
ensure!(
|
||||
row.status == "Valid",
|
||||
"Authenticode status is {} (expected Valid)",
|
||||
@@ -2154,11 +2221,13 @@ fn assert_third_party_signed(row: &SignatureRow) -> Result<()> {
|
||||
.subject
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow!("Authenticode signature has no signer certificate subject"))?;
|
||||
let common_name = certificate_common_name(subject)
|
||||
.ok_or_else(|| anyhow!("Signer subject has no CN= component: {subject}"))?;
|
||||
ensure!(
|
||||
THIRD_PARTY_PUBLISHER_ALLOWLIST.contains(&common_name),
|
||||
"Signer CN '{common_name}' is not an allowlisted third-party publisher"
|
||||
THIRD_PARTY_WINDOWS_SIGNATURE_ALLOWLIST
|
||||
.iter()
|
||||
.any(|(allowed_path, allowed_subject)| {
|
||||
relative.eq_ignore_ascii_case(allowed_path) && subject == *allowed_subject
|
||||
}),
|
||||
"Signer subject '{subject}' is not allowlisted for {relative}"
|
||||
);
|
||||
ensure!(
|
||||
row.ts_subject.is_some(),
|
||||
@@ -2167,10 +2236,10 @@ fn assert_third_party_signed(row: &SignatureRow) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn assert_signed_by_known_publisher(row: &SignatureRow) -> Result<()> {
|
||||
fn assert_signed_by_known_publisher(row: &SignatureRow, relative: &str) -> Result<()> {
|
||||
match assert_fluxer_signed(row) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(fluxer_error) => assert_third_party_signed(row)
|
||||
Err(fluxer_error) => assert_third_party_signed(row, relative)
|
||||
.map_err(|third_party_error| anyhow!("{fluxer_error}; {third_party_error}")),
|
||||
}
|
||||
}
|
||||
@@ -2333,22 +2402,20 @@ fn verify_windows_pe_signatures(
|
||||
));
|
||||
continue;
|
||||
};
|
||||
if let Err(error) = assert_signed_by_known_publisher(row) {
|
||||
if let Err(error) = assert_signed_by_known_publisher(row, &relative) {
|
||||
failures.push(format!("{relative}: {error}"));
|
||||
}
|
||||
}
|
||||
ensure!(
|
||||
failures.is_empty(),
|
||||
"{label}: {} of {} Windows binaries are not signed by '{}':\n{}",
|
||||
"{label}: {} of {} Windows binaries do not have an approved signature:\n{}",
|
||||
failures.len(),
|
||||
files.len(),
|
||||
FLUXER_WINDOWS_SIGNER_COMMON_NAME,
|
||||
failures.join("\n")
|
||||
);
|
||||
println!(
|
||||
"{label}: verified {} Windows binaries signed by '{}'.",
|
||||
files.len(),
|
||||
FLUXER_WINDOWS_SIGNER_COMMON_NAME
|
||||
"{label}: verified {} Windows binary signatures.",
|
||||
files.len()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user