Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
yovko authored Jan 28, 2025
2 parents 1874ca5 + dac9721 commit 6aa111c
Show file tree
Hide file tree
Showing 180 changed files with 2,532 additions and 613 deletions.
1 change: 1 addition & 0 deletions .docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ ENV YARN_CACHE_FOLDER=$WORKDIR/.yarncache

EXPOSE 2368
EXPOSE 4200
EXPOSE 4201
EXPOSE 4173
EXPOSE 41730
EXPOSE 4175
Expand Down
2 changes: 2 additions & 0 deletions .docker/development.entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
# so we need to install dependencies again
yarn install --frozen-lockfile --prefer-offline

yarn nx run-many -t build:ts

# Execute the CMD
exec "$@"
10 changes: 1 addition & 9 deletions .github/scripts/clean.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// NOTE: this file can't use any NPM dependencies because it needs to run even if dependencies aren't installed yet or are corrupted
const {execSync} = require('child_process');

const isDevContainer = process.env.DEVCONTAINER === 'true';

cleanYarnCache();
resetNxCache();
deleteNodeModules();
Expand Down Expand Up @@ -49,13 +47,7 @@ function resetNxCache() {
function cleanYarnCache() {
console.log('Cleaning yarn cache...');
try {
if (isDevContainer) {
// In devcontainer, these directories are mounted from the host so we can't delete them — `yarn cache clean` will fail
// so we delete the contents of the directories instead
execSync('rm -rf .yarncache/* .yarncachecopy/*');
} else {
execSync('yarn cache clean');
}
execSync('rm -rf .yarncache/* .yarncachecopy/*');
} catch (error) {
console.error('Failed to clean yarn cache:', error);
process.exit(1);
Expand Down
100 changes: 100 additions & 0 deletions .github/scripts/setup-docker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
const path = require('path');
const fs = require('fs').promises;
const {spawn} = require('child_process');

/**
* Run a command and stream output to the console
*
* @param {string} command
* @param {string[]} args
* @param {object} options
*/
async function runAndStream(command, args, options) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
stdio: 'inherit',
...options
});

child.on('close', (code) => {
if (code === 0) {
resolve(code);
} else {
reject(new Error(`'${command} ${args.join(' ')}' exited with code ${code}`));
}
});

});
}

/**
* Removes node dependencies and cleans up local caches
*/
function clean() {
require('./clean');
}

/**
* Adjust config.local.json for Docker Compose setup
*/
async function adjustConfig() {
console.log('Adjusting configuration...');
const coreFolder = path.join(__dirname, '../../ghost/core');
const currentConfigPath = path.join(coreFolder, 'config.local.json');
let currentConfig;
try {
currentConfig = require(currentConfigPath);
} catch (err) {
currentConfig = {};
}

currentConfig.database = {
client: 'mysql',
docker: true,
connection: {
host: 'mysql',
user: 'root',
password: 'root',
database: 'ghost'
}
};

currentConfig.adapters = {
...currentConfig.adapters,
Redis: {
host: 'redis',
port: 6379
}
};

currentConfig.server = {
...currentConfig.server,
host: '0.0.0.0',
port: 2368
};

try {
await fs.writeFile(currentConfigPath, JSON.stringify(currentConfig, null, 4));
} catch (err) {
console.error('Failed to write config.local.json', err);
console.log(`Please add the following to config.local.json:\n`, JSON.stringify(currentConfig, null, 4));
process.exit(1);
}
}

async function buildContainer() {
console.log('Building container...');
await runAndStream('docker-compose', ['build'], {});
}

async function runMigrations() {
console.log('Running migrations...');
await runAndStream('docker-compose', ['run', '--rm', '-w', '/home/ghost/ghost/core', 'ghost', 'yarn', 'knex-migrator', 'init'], {cwd: path.join(__dirname, '../../')});
}

(async () => {
clean();
await adjustConfig();
await buildContainer();
await runMigrations();
})();
2 changes: 1 addition & 1 deletion apps/admin-x-activitypub/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tryghost/admin-x-activitypub",
"version": "0.3.51",
"version": "0.3.54",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
10 changes: 5 additions & 5 deletions apps/admin-x-activitypub/src/components/Inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,9 @@ const Inbox: React.FC<InboxProps> = ({layout}) => {
</div>
</div>
<div className={`sticky top-[133px] ml-auto w-full max-w-[300px] max-lg:hidden xxxl:sticky xxxl:right-[40px]`}>
<h2 className='mb-2 text-lg font-semibold'>This is your {layout === 'inbox' ? 'inbox' : 'feed'}</h2>
<p className='mb-6 border-b border-grey-200 pb-6 text-grey-700'>You&apos;ll find {layout === 'inbox' ? 'long-form content' : 'short posts and updates'} from the accounts you follow here.</p>
<h2 className='mb-2 text-lg font-semibold'>You might also like</h2>
<h2 className='mb-1.5 text-lg font-semibold'>This is your {layout === 'inbox' ? 'inbox' : 'feed'}</h2>
<p className='mb-6 text-grey-700'>You&apos;ll find {layout === 'inbox' ? 'long-form content' : 'short posts and updates'} from the accounts you follow here.</p>
<h2 className='mb-1 text-lg font-semibold'>You might also like</h2>
{isLoadingSuggested ? (
<LoadingIndicator size="sm" />
) : (
Expand All @@ -154,7 +154,7 @@ const Inbox: React.FC<InboxProps> = ({layout}) => {
>
<APAvatar author={actor} />
<div className='flex min-w-0 flex-col'>
<span className='block w-full truncate font-bold text-black'>{getName(actor)}</span>
<span className='block w-full truncate font-semibold text-black'>{getName(actor)}</span>
<span className='block w-full truncate text-sm text-grey-600'>{getUsername(actor)}</span>
</div>
</ActivityItem>
Expand All @@ -165,7 +165,7 @@ const Inbox: React.FC<InboxProps> = ({layout}) => {
})}
</ul>
)}
<Button className='mt-4' color='grey' fullWidth={true} label='Explore' onClick={() => updateRoute('search')} />
<Button className='mt-2' color='green' fullWidth={true} label='Explore &rarr;' link={true} onClick={() => updateRoute('search')} />
</div>
</div>
</>
Expand Down
4 changes: 2 additions & 2 deletions apps/admin-x-activitypub/src/components/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const AccountSearchResultItem: React.FC<AccountSearchResultItemProps> = ({accoun
}}/>
<div>
<div className='text-grey-600'>
<span className='font-bold text-black'>{account.name} </span>{account.handle}
<span className='font-semibold text-black'>{account.name} </span>{account.handle}
</div>
<div className='text-sm'>{new Intl.NumberFormat().format(account.followerCount)} followers</div>
</div>
Expand Down Expand Up @@ -124,7 +124,7 @@ const SuggestedProfile: React.FC<SuggestedProfileProps> = ({profile, update}) =>
<APAvatar author={profile.actor}/>
<div>
<div className='text-grey-600'>
<span className='font-bold text-black'>{profile.actor.name} </span>{profile.handle}
<span className='font-semibold text-black'>{profile.actor.name} </span>{profile.handle}
</div>
<div className='text-sm'>{new Intl.NumberFormat().format(profile.followerCount)} followers</div>
</div>
Expand Down
60 changes: 51 additions & 9 deletions apps/admin-x-activitypub/src/components/feed/ArticleModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import APAvatar from '../global/APAvatar';
import APReplyBox from '../global/APReplyBox';
import TableOfContents, {TOCItem} from './TableOfContents';
import getReadingTime from '../../utils/get-reading-time';
import {useDebounce} from 'use-debounce';

interface ArticleModalProps {
activityId: string;
Expand Down Expand Up @@ -48,6 +49,7 @@ const ArticleBody: React.FC<{
fontFamily: SelectOption;
onHeadingsExtracted?: (headings: TOCItem[]) => void;
onIframeLoad?: (iframe: HTMLIFrameElement) => void;
onLoadingChange?: (isLoading: boolean) => void;
}> = ({
heading,
image,
Expand All @@ -57,7 +59,8 @@ const ArticleBody: React.FC<{
lineHeight,
fontFamily,
onHeadingsExtracted,
onIframeLoad
onIframeLoad,
onLoadingChange
}) => {
const site = useBrowseSite();
const siteData = site.data?.site;
Expand Down Expand Up @@ -213,7 +216,6 @@ const ArticleBody: React.FC<{
if (iframeWindow && typeof iframeWindow.resizeIframe === 'function') {
iframeWindow.resizeIframe();
} else {
// Fallback: trigger a resize event
const resizeEvent = new Event('resize');
iframeDocument.dispatchEvent(resizeEvent);
}
Expand Down Expand Up @@ -269,6 +271,11 @@ const ArticleBody: React.FC<{
return () => iframe.removeEventListener('load', handleLoad);
}, [onHeadingsExtracted, onIframeLoad]);

// Update parent when loading state changes
useEffect(() => {
onLoadingChange?.(isLoading);
}, [isLoading, onLoadingChange]);

return (
<div className='w-full pb-6'>
<div className='relative'>
Expand Down Expand Up @@ -526,30 +533,66 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
const currentGridWidth = `${parseInt(currentMaxWidth) - 64}px`;

const [readingProgress, setReadingProgress] = useState(0);
const [isLoading, setIsLoading] = useState(true);

// Add debounced version of setReadingProgress
const [debouncedSetReadingProgress] = useDebounce(setReadingProgress, 100);

const PROGRESS_INCREMENT = 5; // Progress is shown in 5% increments (0%, 5%, 10%, etc.)

useEffect(() => {
const container = document.querySelector('.overflow-y-auto');
const article = document.getElementById('object-content');

const handleScroll = () => {
if (isLoading) {
return;
}

if (!container || !article) {
return;
}

const articleRect = article.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();

const isContentShorterThanViewport = articleRect.height <= containerRect.height;

if (isContentShorterThanViewport) {
debouncedSetReadingProgress(100);
return;
}

const scrolledPast = Math.max(0, containerRect.top - articleRect.top);
const totalHeight = (article as HTMLElement).offsetHeight - (container as HTMLElement).offsetHeight;

const rawProgress = Math.min(Math.max((scrolledPast / totalHeight) * 100, 0), 100);
const progress = Math.round(rawProgress / 5) * 5;
const progress = Math.round(rawProgress / PROGRESS_INCREMENT) * PROGRESS_INCREMENT;

setReadingProgress(progress);
debouncedSetReadingProgress(progress);
};

if (isLoading) {
return;
}

const observer = new MutationObserver(handleScroll);
if (article) {
observer.observe(article, {
childList: true,
subtree: true,
characterData: true
});
}

container?.addEventListener('scroll', handleScroll);
return () => container?.removeEventListener('scroll', handleScroll);
}, []);
handleScroll();

return () => {
container?.removeEventListener('scroll', handleScroll);
observer.disconnect();
};
}, [isLoading, debouncedSetReadingProgress]);

const [tocItems, setTocItems] = useState<TOCItem[]>([]);
const [activeHeadingId, setActiveHeadingId] = useState<string | null>(null);
Expand All @@ -575,7 +618,6 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
return;
}

// Use offsetTop for absolute position within the document
const headingOffset = heading.offsetTop;

container.scrollTo({
Expand All @@ -602,7 +644,6 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
return;
}

// Get all heading elements and their positions
const headings = tocItems
.map(item => doc.getElementById(item.id))
.filter((el): el is HTMLElement => el !== null)
Expand Down Expand Up @@ -677,7 +718,7 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
</div>
<div className='relative z-10 flex w-full min-w-0 flex-col overflow-visible text-[1.5rem]'>
<div className='flex w-full'>
<span className='min-w-0 truncate whitespace-nowrap font-bold'>{actor.name}</span>
<span className='min-w-0 truncate whitespace-nowrap font-semibold'>{actor.name}</span>
</div>
<div className='flex w-full'>
<span className='text-grey-700 after:mx-1 after:font-normal after:text-grey-700 after:content-["·"]'>{getUsername(actor)}</span>
Expand Down Expand Up @@ -845,6 +886,7 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
lineHeight={LINE_HEIGHTS[currentLineHeightIndex]}
onHeadingsExtracted={handleHeadingsExtracted}
onIframeLoad={handleIframeLoad}
onLoadingChange={setIsLoading}
/>
<div className='ml-[-7px]'>
<FeedItemStats
Expand Down
9 changes: 3 additions & 6 deletions apps/admin-x-activitypub/src/components/feed/FeedItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import APAvatar from '../global/APAvatar';
import FeedItemStats from './FeedItemStats';
import clsx from 'clsx';
import getReadingTime from '../../utils/get-reading-time';
import getRelativeTimestamp from '../../utils/get-relative-timestamp';
import getUsername from '../../utils/get-username';
import stripHtml from '../../utils/strip-html';
import {handleProfileClick} from '../../utils/handle-profile-click';
Expand Down Expand Up @@ -161,8 +160,6 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
const timestamp =
new Date(object?.published ?? new Date()).toLocaleDateString('default', {year: 'numeric', month: 'short', day: '2-digit'}) + ', ' + new Date(object?.published ?? new Date()).toLocaleTimeString('default', {hour: '2-digit', minute: '2-digit'});

const date = new Date(object?.published ?? new Date());

const [, setIsCopied] = useState(false);

const contentRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -324,7 +321,7 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
</div>
<div className='relative z-10 flex w-full min-w-0 flex-col overflow-visible text-[1.5rem]'>
<div className='flex w-full'>
<span className='min-w-0 truncate whitespace-nowrap font-bold after:mx-1 after:font-normal after:text-grey-700 after:content-["·"]' data-test-activity-heading>{author.name}</span>
<span className='min-w-0 truncate whitespace-nowrap font-semibold after:mx-1 after:font-normal after:text-grey-700 after:content-["·"]' data-test-activity-heading>{author.name}</span>
<div>{renderTimestamp(object)}</div>
</div>
<div className='flex w-full'>
Expand Down Expand Up @@ -375,7 +372,7 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
<div className='flex w-full items-center justify-between'>
<div className='relative z-10 flex w-full min-w-0 flex-col overflow-visible'>
<div className='flex'>
<span className='min-w-0 truncate whitespace-nowrap font-bold after:mx-1 after:font-normal after:text-grey-700 after:content-["·"]' data-test-activity-heading>{author.name}</span>
<span className='min-w-0 truncate whitespace-nowrap font-semibold after:mx-1 after:font-normal after:text-grey-700 after:content-["·"]' data-test-activity-heading>{author.name}</span>
<div>{renderTimestamp(object)}</div>
</div>
<div className='flex'>
Expand Down Expand Up @@ -432,7 +429,7 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
onClick={e => handleProfileClick(actor, e)}
>{author.name}
</span>
<span className='shrink-0 whitespace-nowrap text-grey-600 before:mr-1 before:content-["·"]' title={`${timestamp}`}>{getRelativeTimestamp(date)}</span>
<span className='shrink-0 whitespace-nowrap text-grey-600 before:mr-1 before:content-["·"]' title={`${timestamp}`}>{renderTimestamp(object)}</span>
</div>
<div className='flex'>
<div className='flex min-h-[73px] w-full min-w-0 flex-col items-start justify-start gap-1'>
Expand Down
Loading

0 comments on commit 6aa111c

Please sign in to comment.